<template>
  <div>
    <v-app-bar app :elevation="2" style="width: 100%; left: 0">
      <div class="header-left">
        <v-menu transition="scale-transition" :offset-x="true" max-width="320">
          <template v-slot:activator="{ on, attrs }">
            <v-btn depressed v-bind="attrs" v-on="on">
              <v-icon>apps</v-icon>
            </v-btn>
          </template>
          <v-card>
            <v-list>
              <v-list-item>
                <v-list-item-avatar>
                  <img
                    :src="
                      $store.getters.userinfo.avatar ||
                      'https://cc.lzsjl.top/leazerfspaceavatar.jpg'
                    "
                    alt="avatar"
                  />
                </v-list-item-avatar>

                <v-list-item-content>
                  <v-list-item-title
                    >{{ $store.getters.userinfo.realname }} /
                    {{ $store.getters.userinfo.nickname }}</v-list-item-title
                  >
                  <v-list-item-subtitle>{{
                    $store.getters.userinfo.email
                  }}</v-list-item-subtitle>
                </v-list-item-content>
              </v-list-item>
            </v-list>

            <v-divider></v-divider>

            <v-card elevation="0" class="rounded-0" max-width="320">
              <v-img
                :aspect-ratio="16 / 9"
                lazy-src="https://cc.lzsjl.top/leazerfspace.jpg"
                class="white--text align-end"
                height="180"
                :src="pptInfo.cover || 'https://cc.lzsjl.top/leazerfspace.jpg'"
              >
                <v-card-title>{{ pptInfo.title }}</v-card-title>
              </v-img>

              <v-card-subtitle class="pb-1">
                {{ pptInfo.intro ? pptInfo.intro : '这里什么都没有' }}
              </v-card-subtitle>

              <v-card-text class="text--primary py-0 px-3">
                <v-chip
                  small
                  outlined
                  label
                  v-for="(tag, index) in pptInfo.tag"
                  :key="index"
                  class="ma-1"
                  color="primary"
                  >{{ tag }}</v-chip
                >
              </v-card-text>

              <v-card-actions class="pt-1">
                <v-spacer></v-spacer>
                <div class="pl-2 text-body-2" style="color: #757575">
                  创建时间：{{ realTime(pptInfo.create_time) }}
                </div>
              </v-card-actions>
            </v-card>

            <v-card-actions>
              <v-spacer></v-spacer>
              <v-btn color="primary" @click="gotoHome"> 返回主页 </v-btn>
            </v-card-actions>
          </v-card>
        </v-menu>
      </div>
      <v-spacer></v-spacer>
      <div class="header-center" style="margin-left: 60px">
        <v-btn-toggle
          dense
          class="mr-2"
          v-model="stateOper"
          @change="stateOperChange"
        >
          <v-tooltip left>
            <template v-slot:activator="{ on, attrs }">
              <v-btn
                :color="operBtnColor"
                :loading="saveLoading"
                v-bind="attrs"
                v-on="on"
              >
                <v-icon>save</v-icon>
              </v-btn>
            </template>
            <span>{{ update_time }}</span>
          </v-tooltip>

          <v-btn :color="operBtnColor">
            <v-icon>undo</v-icon>
          </v-btn>

          <v-btn :color="operBtnColor">
            <v-icon>redo</v-icon>
          </v-btn>
        </v-btn-toggle>
        <v-btn-toggle dense v-model="elOper" @change="operChange()">
          <!-- 选择对象 -->
          <v-btn
            :color="operBtnColor"
            @click="prepareElement('cursor')"
            class="oper-btn"
          >
            <v-icon>near_me</v-icon>
          </v-btn>
          <!-- 添加文字 -->
          <v-btn
            :color="operBtnColor"
            @click="prepareElement('text')"
            class="oper-btn"
          >
            <v-icon>text_fields</v-icon>
          </v-btn>
          <!-- 添加图形 -->
          <v-menu offset-y>
            <template v-slot:activator="{ on, attrs }">
              <v-btn :color="operBtnColor" v-bind="attrs" v-on="on">
                <v-icon>pentagon</v-icon>
              </v-btn>
            </template>
            <v-container class="white">
              <v-row no-gutters><v-col>基础图形</v-col></v-row>
              <v-row no-gutters>
                <v-col>
                  <v-btn
                    icon
                    tile
                    class="oper-btn"
                    @click="prepareElement('rect')"
                  >
                    <v-icon large>$vuetify.icons.rect</v-icon>
                  </v-btn>
                </v-col>
                <v-col>
                  <v-btn
                    icon
                    tile
                    class="oper-btn"
                    @click="prepareElement('circle')"
                  >
                    <v-icon large>$vuetify.icons.circle</v-icon>
                  </v-btn>
                </v-col>
                <v-col>
                  <v-btn
                    icon
                    tile
                    class="oper-btn"
                    @click="prepareElement('triangle')"
                  >
                    <v-icon large>$vuetify.icons.triangle</v-icon>
                  </v-btn>
                </v-col>
              </v-row>
              <!-- 暂且不做 -->
              <!-- <v-row no-gutters><v-col>自定义图形</v-col></v-row>
              <v-row no-gutters>
                <v-col>
                  <v-btn
                    icon
                    tile
                    class="oper-btn"
                    @click="prepareElement('polygon')"
                  >
                    <v-icon large>$vuetify.icons.polygon</v-icon>
                  </v-btn>
                </v-col>
              </v-row> -->
            </v-container>
          </v-menu>
          <!-- 添加线条 -->
          <v-menu offset-y>
            <template v-slot:activator="{ on, attrs }">
              <v-btn :color="operBtnColor" v-bind="attrs" v-on="on">
                <v-icon>south_east</v-icon>
              </v-btn>
            </template>
            <v-container class="white">
              <v-row no-gutters><v-col>基础线条</v-col></v-row>
              <v-row no-gutters>
                <v-col>
                  <v-btn
                    icon
                    tile
                    class="oper-btn"
                    @click="prepareElement('line')"
                  >
                    <v-icon large>$vuetify.icons.line</v-icon>
                  </v-btn>
                </v-col>
                <v-col>
                  <v-btn
                    icon
                    tile
                    class="oper-btn"
                    @click="prepareElement('arrowTop')"
                  >
                    <v-icon large>$vuetify.icons.arrowTop</v-icon>
                  </v-btn>
                </v-col>
                <v-col>
                  <v-btn
                    icon
                    tile
                    class="oper-btn"
                    @click="prepareElement('arrowBottom')"
                  >
                    <v-icon large>$vuetify.icons.arrowBottom</v-icon>
                  </v-btn>
                </v-col>
                <v-col>
                  <v-btn
                    icon
                    tile
                    class="oper-btn"
                    @click="prepareElement('arrow')"
                  >
                    <v-icon large>$vuetify.icons.arrow</v-icon>
                  </v-btn>
                </v-col>
              </v-row>
              <v-row no-gutters><v-col>画笔</v-col></v-row>
              <v-row no-gutters>
                <v-col>
                  <v-btn
                    icon
                    tile
                    class="oper-btn"
                    @click="prepareElement('pen')"
                  >
                    <v-icon large>$vuetify.icons.pen</v-icon>
                  </v-btn>
                </v-col>
              </v-row>
            </v-container>
          </v-menu>

          <v-menu offset-y :close-on-content-click="false" v-model="imgMenu">
            <template v-slot:activator="{ on, attrs }">
              <v-btn :color="operBtnColor" v-bind="attrs" v-on="on">
                <v-icon>image</v-icon>
              </v-btn>
            </template>
            <v-container class="white">
              <v-btn color="primary " @click="addImg()" block class="mt-2"
                >添加本地图片</v-btn
              >
              <input
                type="file"
                @change="uploadImgChange"
                id="imgInput"
                accept="image/*"
                style="display: none"
              />
              <v-form v-model="valid" lazy-validation ref="imgUrlForm">
                <v-text-field
                  v-model="imgUrl"
                  label="图片链接"
                  required
                  :rules="imgUrlRules"
                  clearable
                ></v-text-field>
                <div class="d-flex flex-row-reverse">
                  <v-btn color="primary" @click="addImgUrl()" small
                    >添加网络图片</v-btn
                  >
                </div>
              </v-form>
            </v-container>
          </v-menu>

          <v-menu offset-y :close-on-content-click="false" v-model="urlMenu">
            <template v-slot:activator="{ on, attrs }">
              <v-btn :color="operBtnColor" v-bind="attrs" v-on="on">
                <v-icon>link</v-icon>
              </v-btn>
            </template>
            <v-container class="white">
              <v-form v-model="valid" lazy-validation ref="urlForm">
                <v-text-field
                  v-model="webName"
                  label="名称"
                  required
                  :rules="webNameRules"
                ></v-text-field>
                <v-text-field
                  v-model="webUrl"
                  label="链接"
                  required
                  :rules="webUrlRules"
                ></v-text-field>
                <div class="d-flex flex-row-reverse">
                  <v-btn color="primary" @click="addUrl()" small>添加</v-btn>
                </div>
              </v-form>
            </v-container>
          </v-menu>
          <v-btn :color="operBtnColor" @click="openWebFrame">
            <v-icon>web_asset</v-icon>
          </v-btn>
        </v-btn-toggle>
      </div>
      <v-spacer></v-spacer>
      <div class="header-right">
        <v-btn
          class="mr-2"
          color="light-blue darken-3"
          dark
          depressed
          @click="openShowPPT()"
        >
          放映
          <v-icon right> subscriptions </v-icon>
        </v-btn>
        <v-btn
          class="mr-2"
          color="orange darken-3"
          dark
          depressed
          @click="changeScreen()"
        >
          全屏
          <v-icon right> {{ expendIcon }} </v-icon>
        </v-btn>
        <v-btn
          color="blue-grey"
          dark
          depressed
          @click.stop="drawerRight = !drawerRight"
        >
          格式
          <v-icon right> segment </v-icon>
        </v-btn>
      </div>
    </v-app-bar>

    <v-navigation-drawer
      app
      permanent
      class="grey lighten-5"
      style="top: 64px; max-height: calc(100% - 64px); z-index: 4"
    >
      <v-btn-toggle
        dense
        class="format-control"
        mandatory
        v-model="leftTab"
        tile
      >
        <v-btn text tile> 页面 </v-btn>
        <v-btn text tile> 元素 </v-btn>
      </v-btn-toggle>
      <div
        style="height: calc(100% - 36px); width: 100%; overflow-y: auto"
        class="left-drawer"
      >
        <div v-show="leftTab === 0">
          <div>
            <v-list class="py-0">
              <v-list-item-group color="primary" v-model="whichCanv" mandatory>
                <v-list-item
                  v-for="(item, index) in canvList"
                  :key="index"
                  @contextmenu="showPPTMenu($event, index)"
                  @click.left="changePPT(index)"
                >
                  <v-list-item-content>
                    <div style="display: flex">
                      <v-list-item-title v-text="index + 1"></v-list-item-title>
                      <v-img
                        :aspect-ratio="16 / 9"
                        lazy-src="https://cc.lzsjl.top/leazerfspacewait.jpg"
                        height="90"
                        width="160"
                        :src="svgArray[index]"
                        :class="{ 'has-border': whichCanv === index }"
                      ></v-img>
                    </div>
                  </v-list-item-content>
                </v-list-item>
              </v-list-item-group>
            </v-list>
            <v-btn
              block
              tile
              color="primary"
              dark
              depressed
              class="py-6 mb-16 mt-4"
              @click="addPPT()"
              >新建幻灯片</v-btn
            >
          </div>
        </div>
        <div v-show="leftTab === 1">
          <div>
            <v-list class="py-0">
              <v-list-item-group
                v-model="selectedCanvObjs"
                color="primary"
                multiple
                @change="changeSelectedCanvObjs()"
              >
                <v-list-item
                  v-for="(item, index) in canvObjs"
                  :key="index"
                  @contextmenu="showObjMenu($event, index)"
                >
                  <v-list-item-content>
                    <v-list-item-title
                      >[{{ index + 1 }}] {{ item.get('type') }}-<span
                        style="font-size: 8px"
                        >{{ item.id }}</span
                      ></v-list-item-title
                    >
                  </v-list-item-content>
                </v-list-item>
              </v-list-item-group>
            </v-list>
          </div>
        </div>
      </div>
      <v-menu
        v-model="pptMenuShow"
        :position-x="pptMenuX"
        :position-y="pptMenuY"
        absolute
        offset-y
        transition="false"
      >
        <v-list class="py-0">
          <v-list-item-group
            v-model="pptMenuOper"
            color="primary"
            @change="choosePPTMenuOper"
          >
            <v-list-item>
              <v-list-item-content>
                <v-list-item-title>新建幻灯片</v-list-item-title>
              </v-list-item-content>
            </v-list-item>
            <v-list-item>
              <v-list-item-content>
                <v-list-item-title>删除幻灯片</v-list-item-title>
              </v-list-item-content>
            </v-list-item>
            <v-list-item>
              <v-list-item-content>
                <v-list-item-title>复制幻灯片</v-list-item-title>
              </v-list-item-content>
            </v-list-item>
            <v-list-item>
              <v-list-item-content>
                <v-list-item-title>剪切幻灯片</v-list-item-title>
              </v-list-item-content>
            </v-list-item>
            <v-list-item>
              <v-list-item-content>
                <v-list-item-title>粘贴幻灯片</v-list-item-title>
              </v-list-item-content>
            </v-list-item>
          </v-list-item-group>
        </v-list>
      </v-menu>
      <v-menu
        v-model="objMenuShow"
        :position-x="objMenuX"
        :position-y="objMenuY"
        absolute
        offset-y
        transition="false"
      >
        <v-list class="py-0 obj-menu">
          <v-list-item-group
            v-model="objMenuOper"
            color="primary"
            @change="chooseObjMenuOper"
          >
            <v-list-item>
              <v-list-item-content>
                <v-list-item-title>复制对象</v-list-item-title>
              </v-list-item-content>
            </v-list-item>
            <v-list-item>
              <v-list-item-content>
                <v-list-item-title>剪切对象</v-list-item-title>
              </v-list-item-content>
            </v-list-item>
            <v-list-item>
              <v-list-item-content>
                <v-list-item-title>粘贴对象</v-list-item-title>
              </v-list-item-content>
            </v-list-item>
            <v-list-item>
              <v-list-item-content>
                <v-list-item-title>删除对象</v-list-item-title>
              </v-list-item-content>
            </v-list-item>
            <v-list-item>
              <v-list-item-content>
                <v-list-item-title>上移一层</v-list-item-title>
              </v-list-item-content>
            </v-list-item>
            <v-list-item>
              <v-list-item-content>
                <v-list-item-title>下移一层</v-list-item-title>
              </v-list-item-content>
            </v-list-item>
            <v-list-item>
              <v-list-item-content>
                <v-list-item-title>置于顶层</v-list-item-title>
              </v-list-item-content>
            </v-list-item>
            <v-list-item>
              <v-list-item-content>
                <v-list-item-title>置于底层</v-list-item-title>
              </v-list-item-content>
            </v-list-item>
          </v-list-item-group>
        </v-list>
      </v-menu>
    </v-navigation-drawer>

    <v-navigation-drawer
      app
      right
      disable-resize-watcher
      v-model="drawerRight"
      class="grey lighten-5 right-drawer"
      width="280"
      stateless
      style="top: 64px; max-height: calc(100% - 64px); z-index: 4"
    >
      <!-- 对象设置切换按钮 -->
      <v-btn-toggle
        dense
        class="format-control"
        mandatory
        v-model="rightTab"
        tile
        v-show="nowType !== 'canvas'"
      >
        <v-btn text tile> 样式 </v-btn>
        <v-btn text tile v-show="nowType === 'textbox'"> 文本 </v-btn>
        <v-btn text tile> 排列 </v-btn>
      </v-btn-toggle>
      <!-- 画布样式设置切换按钮 -->
      <v-btn-toggle
        dense
        class="format-control"
        tile
        v-model="canvTab"
        v-show="nowType === 'canvas'"
        @change="changeCanvTab()"
      >
        <v-btn text tile> 画布样式 </v-btn>
      </v-btn-toggle>
      <!-- 画布样式设置 -->
      <div
        style="height: calc(100% - 36px); width: 100%; overflow-y: auto"
        class="right-tab"
        v-show="nowType === 'canvas'"
      >
        <div>
          <v-expansion-panels v-model="canvPanel" multiple accordion tile flat>
            <v-expansion-panel>
              <v-expansion-panel-header
                ><div class="font-weight-bold">
                  背景
                </div></v-expansion-panel-header
              >
              <v-expansion-panel-content>
                <v-row>
                  <v-col cols="4" class="d-flex align-center"
                    ><div class="text-body-2 pl-3">填充颜色</div></v-col
                  >
                  <v-col cols="8">
                    <v-menu
                      offset-y
                      :close-on-content-click="false"
                      v-model="canvBkColorMenu"
                    >
                      <template v-slot:activator="{ on, attrs }">
                        <v-btn
                          v-bind="attrs"
                          v-on="on"
                          small
                          block
                          :color="canvBkColor"
                          class="fill-color-btn"
                        ></v-btn>
                      </template>
                      <v-container class="white right-drawer-out">
                        <v-color-picker
                          show-swatches
                          v-model="canvBkColor"
                          @input="changeCanvBkColor()"
                        ></v-color-picker>
                      </v-container>
                    </v-menu>
                  </v-col>
                </v-row>
                <v-row>
                  <v-container class="white pl-6">
                    <v-btn
                      color="primary "
                      @click="changeCanvImg()"
                      block
                      class="mt-2"
                      width="100%"
                      >添加本地图片</v-btn
                    >
                    <input
                      type="file"
                      @change="uploadCanvImgChange"
                      id="canvImgInput"
                      accept="image/*"
                      style="display: none"
                    />
                    <v-form
                      v-model="valid"
                      lazy-validation
                      ref="canvImgUrlForm"
                    >
                      <v-text-field
                        v-model="canvImgUrl"
                        label="图片链接"
                        required
                        :rules="imgUrlRules"
                        clearable
                      ></v-text-field>
                      <div class="d-flex flex-row-reverse">
                        <v-btn color="primary" @click="addCanvImg()" small
                          >添加网络图片</v-btn
                        >
                      </div>
                    </v-form>
                  </v-container>
                </v-row>
              </v-expansion-panel-content>
            </v-expansion-panel>
          </v-expansion-panels>
        </div>
      </div>
      <!-- 对象设置 -->
      <div
        style="height: calc(100% - 36px); width: 100%; overflow-y: auto"
        class="right-tab"
        v-show="nowType !== 'canvas'"
      >
        <div v-show="rightTab === 0">
          <v-expansion-panels v-model="stylePanel" multiple accordion tile flat>
            <v-expansion-panel
              v-show="!['image', 'line', 'path'].includes(nowType)"
            >
              <v-expansion-panel-header
                ><div class="font-weight-bold">
                  {{ nowType === 'textbox' ? '颜色' : '填充' }}
                </div></v-expansion-panel-header
              >
              <v-expansion-panel-content>
                <v-row>
                  <v-col cols="4" class="d-flex align-center"
                    ><div class="text-body-2 pl-3">
                      {{ nowType === 'textbox' ? '字体颜色' : '填充颜色' }}
                    </div></v-col
                  >
                  <v-col cols="8">
                    <v-menu
                      offset-y
                      :close-on-content-click="false"
                      v-model="fillColorMenu"
                    >
                      <template v-slot:activator="{ on, attrs }">
                        <v-btn
                          v-bind="attrs"
                          v-on="on"
                          small
                          block
                          :color="fillColor"
                          class="fill-color-btn"
                        ></v-btn>
                      </template>
                      <v-container class="white right-drawer-out">
                        <v-color-picker
                          show-swatches
                          v-model="fillColor"
                          @input="changeFillColor()"
                        ></v-color-picker>
                      </v-container>
                    </v-menu>
                  </v-col>
                </v-row>
                <v-row v-show="nowType === 'textbox'">
                  <v-col cols="4" class="d-flex align-center"
                    ><div class="text-body-2 pl-3">背景颜色</div></v-col
                  >
                  <v-col cols="8">
                    <v-menu
                      offset-y
                      :close-on-content-click="false"
                      v-model="bkColorMenu"
                    >
                      <template v-slot:activator="{ on, attrs }">
                        <v-btn
                          v-bind="attrs"
                          v-on="on"
                          small
                          block
                          :color="bkColor"
                          class="fill-color-btn"
                        ></v-btn>
                      </template>
                      <v-container class="white right-drawer-out">
                        <v-color-picker
                          show-swatches
                          v-model="bkColor"
                          @input="changeBkColor()"
                        ></v-color-picker>
                      </v-container>
                    </v-menu>
                  </v-col>
                </v-row>
              </v-expansion-panel-content>
            </v-expansion-panel>
            <v-expansion-panel>
              <v-expansion-panel-header
                ><div class="font-weight-bold">
                  边框
                </div></v-expansion-panel-header
              >
              <v-expansion-panel-content>
                <v-row>
                  <v-col cols="4" class="d-flex align-center"
                    ><div class="text-body-2 pl-3">边框颜色</div></v-col
                  >
                  <v-col cols="8">
                    <v-menu
                      offset-y
                      :close-on-content-click="false"
                      v-model="strokeColorMenu"
                    >
                      <template v-slot:activator="{ on, attrs }">
                        <v-btn
                          v-bind="attrs"
                          v-on="on"
                          small
                          block
                          :color="strokeColor"
                          class="fill-color-btn"
                        ></v-btn>
                      </template>
                      <v-container class="white right-drawer-out">
                        <v-color-picker
                          show-swatches
                          v-model="strokeColor"
                          @input="changeStrokeColor()"
                        ></v-color-picker>
                      </v-container>
                    </v-menu>
                  </v-col>
                </v-row>
                <v-row>
                  <v-col cols="4" class="d-flex align-center"
                    ><div class="text-body-2 pl-3">边框宽度</div></v-col
                  >
                  <v-col cols="8">
                    <v-text-field
                      dense
                      hide-details
                      min="0"
                      type="number"
                      v-Int
                      v-model="strokeWidth"
                      @input="changeStrokeWidth()"
                    ></v-text-field>
                  </v-col>
                </v-row>
                <v-row v-show="nowType === 'rect' || nowType === 'line'">
                  <v-col cols="4" class="d-flex align-center"
                    ><div class="text-body-2 pl-3">边框类型</div></v-col
                  >
                  <v-col cols="8">
                    <!-- 这里的弹出菜单需要做点击处理，也就是先获知其id，然后在点击事件中跳过他 -->
                    <v-select
                      class="right-drawer-out out-menu-1"
                      :items="['实线', '虚线']"
                      v-model="strokeType"
                      dense
                      hide-details
                      @change="changeStrokeType()"
                      attach=".out-menu-1"
                    ></v-select>
                  </v-col>
                </v-row>
              </v-expansion-panel-content>
            </v-expansion-panel>
            <v-expansion-panel v-show="nowType === 'rect'">
              <v-expansion-panel-header
                ><div class="font-weight-bold">
                  圆角
                </div></v-expansion-panel-header
              >
              <v-expansion-panel-content>
                <v-row>
                  <v-col cols="4" class="d-flex align-center"
                    ><div class="text-body-2 pl-3">横向宽度</div></v-col
                  >
                  <v-col cols="8">
                    <v-text-field
                      dense
                      hide-details
                      type="number"
                      v-model="radiusX"
                      min="0"
                      v-Int
                      @input="changeRadiusX()"
                    ></v-text-field>
                  </v-col>
                </v-row>
                <v-row>
                  <v-col cols="4" class="d-flex align-center"
                    ><div class="text-body-2 pl-3">纵向宽度</div></v-col
                  >
                  <v-col cols="8">
                    <v-text-field
                      dense
                      hide-details
                      min="0"
                      type="number"
                      v-Int
                      v-model="radiusY"
                      @input="changeRadiusY()"
                    ></v-text-field>
                  </v-col>
                </v-row>
              </v-expansion-panel-content>
            </v-expansion-panel>
            <v-expansion-panel
              v-show="
                ['rect', 'circle', 'triangle', 'textbox'].includes(nowType)
              "
            >
              <v-expansion-panel-header
                ><div class="font-weight-bold">
                  阴影
                </div></v-expansion-panel-header
              >
              <v-expansion-panel-content>
                <v-row>
                  <v-col cols="4" class="d-flex align-center"
                    ><div class="text-body-2 pl-3">阴影颜色</div></v-col
                  >
                  <v-col cols="8">
                    <v-menu
                      offset-y
                      :close-on-content-click="false"
                      v-model="shadowColorMenu"
                    >
                      <template v-slot:activator="{ on, attrs }">
                        <v-btn
                          v-bind="attrs"
                          v-on="on"
                          small
                          block
                          :color="shadowColor"
                          class="fill-color-btn"
                        ></v-btn>
                      </template>
                      <v-container class="white right-drawer-out">
                        <v-color-picker
                          show-swatches
                          v-model="shadowColor"
                          @input="changeShadowColor()"
                        ></v-color-picker>
                      </v-container>
                    </v-menu>
                  </v-col>
                </v-row>
                <v-row>
                  <v-col cols="4" class="d-flex align-center"
                    ><div class="text-body-2 pl-3">模糊距离</div></v-col
                  >
                  <v-col cols="8">
                    <v-text-field
                      dense
                      hide-details
                      type="number"
                      min="0"
                      v-model="shadowBlur"
                      v-Int
                      @input="changeShadowBlur()"
                    ></v-text-field>
                  </v-col>
                </v-row>
                <v-row>
                  <v-col cols="4" class="d-flex align-center"
                    ><div class="text-body-2 pl-3">横向距离</div></v-col
                  >
                  <v-col cols="8">
                    <v-text-field
                      dense
                      hide-details
                      type="number"
                      v-model="shadowX"
                      @input="changeShadowX()"
                    ></v-text-field>
                  </v-col>
                </v-row>
                <v-row>
                  <v-col cols="4" class="d-flex align-center"
                    ><div class="text-body-2 pl-3">纵向距离</div></v-col
                  >
                  <v-col cols="8">
                    <v-text-field
                      dense
                      hide-details
                      type="number"
                      v-model="shadowY"
                      @input="changeShadowY()"
                    ></v-text-field>
                  </v-col>
                </v-row>
              </v-expansion-panel-content>
            </v-expansion-panel>
            <v-expansion-panel class="overflow-hidden">
              <v-expansion-panel-header
                ><div class="font-weight-bold">
                  不透明度
                </div></v-expansion-panel-header
              >
              <v-expansion-panel-content>
                <v-row>
                  <v-col cols="4" class="d-flex align-center"
                    ><div class="text-body-2 pl-3">不透明度</div></v-col
                  >
                  <v-col cols="8">
                    <v-slider
                      hint="不透明度"
                      max="1"
                      min="0"
                      step="0.1"
                      thumb-label
                      dense
                      hide-details
                      :thumb-size="26"
                      v-model="opacity"
                      @input="changeOpacity()"
                    ></v-slider>
                  </v-col>
                </v-row>
              </v-expansion-panel-content>
            </v-expansion-panel>
          </v-expansion-panels>
        </div>
        <div v-show="rightTab === 1">
          <v-expansion-panels v-model="textPanel" multiple accordion tile flat>
            <v-expansion-panel>
              <v-expansion-panel-header
                ><div class="font-weight-bold">
                  字体
                </div></v-expansion-panel-header
              >
              <v-expansion-panel-content>
                <v-row>
                  <v-col cols="4" class="d-flex align-center"
                    ><div class="text-body-2 pl-3">字体系列</div></v-col
                  >
                  <v-col cols="8">
                    <v-select
                      class="right-drawer-out out-menu-2"
                      :items="fontFamilyArray"
                      v-model="fontFamily"
                      dense
                      hide-details
                      @change="changeFontFamily()"
                      attach=".out-menu-2"
                    ></v-select>
                  </v-col>
                </v-row>
                <v-row>
                  <v-col cols="4" class="d-flex align-center"
                    ><div class="text-body-2 pl-3">文字大小</div></v-col
                  >
                  <v-col cols="8">
                    <v-combobox
                      hide-selected
                      dense
                      hide-details
                      type="number"
                      v-Int
                      class="right-drawer-out out-menu-3"
                      :items="fontSizeArray"
                      v-model="fontSize"
                      @input="changeFontSize()"
                      attach=".out-menu-3"
                    ></v-combobox>
                  </v-col>
                </v-row>
                <v-row>
                  <v-col cols="4" class="d-flex align-center"
                    ><div class="text-body-2 pl-3">文字间距</div></v-col
                  >
                  <v-col cols="8">
                    <v-text-field
                      dense
                      hide-details
                      type="number"
                      v-model="charSpacing"
                      min="0"
                      v-Int
                      suffix="/10字"
                      @input="changeCharSpacing()"
                    ></v-text-field>
                  </v-col>
                </v-row>
                <v-row>
                  <v-col cols="4" class="d-flex align-center"
                    ><div class="text-body-2 pl-3">文本行高</div></v-col
                  >
                  <v-col cols="8">
                    <v-text-field
                      dense
                      hide-details
                      type="number"
                      v-model="lineHeight"
                      min="0"
                      v-Int
                      suffix="/10倍"
                      @input="changeLineHeight()"
                    ></v-text-field>
                  </v-col>
                </v-row>
              </v-expansion-panel-content>
            </v-expansion-panel>
            <v-expansion-panel
              v-show="nowType === 'textbox' && nowObj && nowObj.webUrl"
            >
              <v-expansion-panel-header
                ><div class="font-weight-bold">
                  链接
                </div></v-expansion-panel-header
              >
              <v-expansion-panel-content>
                <v-row>
                  <v-col cols="4" class="d-flex align-center"
                    ><div class="text-body-2 pl-3">链接地址</div></v-col
                  >
                  <v-col cols="8">
                    <v-form v-model="valid" lazy-validation ref="newUrlForm">
                      <v-text-field
                        dense
                        hide-details
                        v-model="newUrl"
                        required
                        :rules="webUrlRules"
                        clearable
                        @input="changeNewUrl()"
                      ></v-text-field>
                    </v-form>
                  </v-col>
                </v-row>
              </v-expansion-panel-content>
            </v-expansion-panel>
            <v-expansion-panel>
              <v-expansion-panel-header
                ><div class="font-weight-bold">
                  修饰
                </div></v-expansion-panel-header
              >
              <v-expansion-panel-content>
                <v-btn-toggle
                  v-model="decorateWhich"
                  dense
                  @change="handleDecorate()"
                >
                  <v-btn>
                    <v-icon>format_bold</v-icon>
                  </v-btn>
                  <v-btn>
                    <v-icon>format_italic</v-icon>
                  </v-btn>
                  <v-btn>
                    <v-icon>format_underlined</v-icon>
                  </v-btn>
                  <v-btn>
                    <v-icon>strikethrough_s</v-icon>
                  </v-btn>
                  <v-btn>
                    <v-icon>superscript</v-icon>
                  </v-btn>
                  <v-btn>
                    <v-icon>subscript</v-icon>
                  </v-btn>
                </v-btn-toggle>
              </v-expansion-panel-content>
            </v-expansion-panel>
            <v-expansion-panel>
              <v-expansion-panel-header
                ><div class="font-weight-bold">
                  对齐
                </div></v-expansion-panel-header
              >
              <v-expansion-panel-content>
                <v-btn-toggle
                  v-model="textAlignWhich"
                  dense
                  @change="handleTextAlign()"
                >
                  <v-btn>
                    <v-icon>align_horizontal_left</v-icon>
                  </v-btn>
                  <v-btn>
                    <v-icon>align_horizontal_center</v-icon>
                  </v-btn>
                  <v-btn>
                    <v-icon>align_horizontal_right</v-icon>
                  </v-btn>
                </v-btn-toggle>
              </v-expansion-panel-content>
            </v-expansion-panel>
          </v-expansion-panels>
        </div>
        <div v-show="rightTab === 2">
          <v-expansion-panels v-model="alignPanel" multiple accordion tile flat>
            <v-expansion-panel>
              <v-expansion-panel-header
                ><div class="font-weight-bold">
                  排列
                </div></v-expansion-panel-header
              >
              <v-expansion-panel-content>
                <v-btn-toggle v-model="alignWhich" dense @change="handlerAlign">
                  <v-btn>
                    <v-icon>border_left</v-icon>
                  </v-btn>
                  <v-btn>
                    <v-icon>border_vertical</v-icon>
                  </v-btn>
                  <v-btn>
                    <v-icon>border_right</v-icon>
                  </v-btn>
                  <v-btn>
                    <v-icon>border_inner</v-icon>
                  </v-btn>
                  <v-btn>
                    <v-icon>border_top</v-icon>
                  </v-btn>
                  <v-btn>
                    <v-icon>border_horizontal</v-icon>
                  </v-btn>
                  <v-btn>
                    <v-icon>border_bottom</v-icon>
                  </v-btn>
                </v-btn-toggle>
                <v-btn-toggle
                  v-model="placeWhich"
                  dense
                  @change="handlerPlace"
                  class="mt-2"
                >
                  <v-btn> 上移 </v-btn>
                  <v-btn> 下移 </v-btn>
                  <v-btn> 置顶 </v-btn>
                  <v-btn> 置底 </v-btn>
                </v-btn-toggle>
              </v-expansion-panel-content>
            </v-expansion-panel>
            <v-expansion-panel
              v-show="!['line', 'path', 'textbox'].includes(nowType)"
            >
              <v-expansion-panel-header
                ><div class="font-weight-bold">
                  大小
                </div></v-expansion-panel-header
              >
              <v-expansion-panel-content>
                <v-row>
                  <v-col cols="4" class="d-flex align-center"
                    ><div class="text-body-2 pl-3">实际宽度</div></v-col
                  >
                  <v-col cols="8">
                    <v-text-field
                      dense
                      hide-details
                      type="number"
                      v-model="objWidth"
                      min="0"
                      v-Int
                      @input="changeObjWidth()"
                    ></v-text-field>
                  </v-col>
                </v-row>
                <v-row>
                  <v-col cols="4" class="d-flex align-center"
                    ><div class="text-body-2 pl-3">实际高度</div></v-col
                  >
                  <v-col cols="8">
                    <v-text-field
                      dense
                      hide-details
                      type="number"
                      v-model="objHeight"
                      min="0"
                      v-Int
                      @input="changeObjHeight()"
                    ></v-text-field>
                  </v-col>
                </v-row>
                <v-row>
                  <v-col cols="4" class="d-flex align-center"
                    ><div class="text-body-2 pl-3">固定比例</div></v-col
                  >
                  <v-col cols="8">
                    <v-checkbox
                      dense
                      hide-details
                      v-model="isAspectRatio"
                      :label="`宽高比${isAspectRatio ? '已固定' : '未固定'}`"
                    ></v-checkbox>
                  </v-col>
                </v-row>
              </v-expansion-panel-content>
            </v-expansion-panel>
            <v-expansion-panel>
              <v-expansion-panel-header
                ><div class="font-weight-bold">
                  位置
                </div></v-expansion-panel-header
              >
              <v-expansion-panel-content>
                <v-row>
                  <v-col cols="4" class="d-flex align-center"
                    ><div class="text-body-2 pl-3">横向位置</div></v-col
                  >
                  <v-col cols="8">
                    <v-text-field
                      dense
                      hide-details
                      type="number"
                      v-model="objX"
                      @input="changeObjX()"
                    ></v-text-field>
                  </v-col>
                </v-row>
                <v-row>
                  <v-col cols="4" class="d-flex align-center"
                    ><div class="text-body-2 pl-3">纵向位置</div></v-col
                  >
                  <v-col cols="8">
                    <v-text-field
                      dense
                      hide-details
                      type="number"
                      v-model="objY"
                      @input="changeObjY()"
                    ></v-text-field>
                  </v-col>
                </v-row>
              </v-expansion-panel-content>
            </v-expansion-panel>
            <v-expansion-panel class="overflow-hidden">
              <v-expansion-panel-header
                ><div class="font-weight-bold">
                  旋转
                </div></v-expansion-panel-header
              >
              <v-expansion-panel-content>
                <v-row>
                  <v-col cols="4" class="d-flex align-center"
                    ><div class="text-body-2 pl-3">旋转角度</div></v-col
                  >
                  <v-col cols="8">
                    <v-text-field
                      dense
                      hide-details
                      type="number"
                      v-model="objAngle"
                      @input="changeObjAngle()"
                    ></v-text-field>
                  </v-col>
                </v-row>
              </v-expansion-panel-content>
            </v-expansion-panel>
            <v-expansion-panel
              v-show="nowType === 'rect' || nowType === 'triangle'"
            >
              <v-expansion-panel-header
                ><div class="font-weight-bold">
                  斜度
                </div></v-expansion-panel-header
              >
              <v-expansion-panel-content>
                <v-row>
                  <v-col cols="4" class="d-flex align-center"
                    ><div class="text-body-2 pl-3">横向斜度</div></v-col
                  >
                  <v-col cols="8">
                    <v-text-field
                      dense
                      hide-details
                      type="number"
                      v-model="skewX"
                      @input="changeSkewX()"
                    ></v-text-field>
                  </v-col>
                </v-row>
                <v-row>
                  <v-col cols="4" class="d-flex align-center"
                    ><div class="text-body-2 pl-3">纵向斜度</div></v-col
                  >
                  <v-col cols="8">
                    <v-text-field
                      dense
                      hide-details
                      type="number"
                      v-model="skewY"
                      @input="changeSkewY()"
                    ></v-text-field>
                  </v-col>
                </v-row>
              </v-expansion-panel-content>
            </v-expansion-panel>
          </v-expansion-panels>
        </div>
      </div>
    </v-navigation-drawer>

    <v-main class="grey lighten-3" style="height: 100vh">
      <!-- 这里可以去掉 -->
      <!-- <div class="tcanv-wrap" v-show="false">
        <canvas id="tcanv" ref="tcanv"></canvas>
      </div> -->
      <div class="canv-wrap" ref="canvWrap" id="canvWrap">
        <div
          class="canv-main"
          ref="canvMain"
          id="canvMain"
          style="position: absolute; top: 0; left: 0"
          @contextmenu="showCanvMenu($event)"
        >
          <canvas id="canvas" ref="canvas"></canvas>
        </div>
      </div>
      <v-menu
        v-model="canvMenuShow"
        :position-x="canvMenuX"
        :position-y="canvMenuY"
        absolute
        offset-y
        transition="false"
      >
        <v-list class="py-0 canv-menu">
          <v-list-item-group
            v-model="canvMenuOper"
            color="primary"
            @change="chooseCanvMenuOper"
          >
            <v-list-item>
              <v-list-item-content>
                <v-list-item-title>复制对象</v-list-item-title>
              </v-list-item-content>
            </v-list-item>
            <v-list-item>
              <v-list-item-content>
                <v-list-item-title>剪切对象</v-list-item-title>
              </v-list-item-content>
            </v-list-item>
            <v-list-item>
              <v-list-item-content>
                <v-list-item-title>粘贴对象</v-list-item-title>
              </v-list-item-content>
            </v-list-item>
            <v-list-item>
              <v-list-item-content>
                <v-list-item-title>删除对象</v-list-item-title>
              </v-list-item-content>
            </v-list-item>
            <v-list-item>
              <v-list-item-content>
                <v-list-item-title>上移一层</v-list-item-title>
              </v-list-item-content>
            </v-list-item>
            <v-list-item>
              <v-list-item-content>
                <v-list-item-title>下移一层</v-list-item-title>
              </v-list-item-content>
            </v-list-item>
            <v-list-item>
              <v-list-item-content>
                <v-list-item-title>置于顶层</v-list-item-title>
              </v-list-item-content>
            </v-list-item>
            <v-list-item>
              <v-list-item-content>
                <v-list-item-title>置于底层</v-list-item-title>
              </v-list-item-content>
            </v-list-item>
          </v-list-item-group>
        </v-list>
      </v-menu>
    </v-main>
  </div>
</template>

<script>
import { fabric } from 'fabric'
import $ from 'jquery'
import { upload } from '@/api/file'
import { getOnePPT, updatePPT } from '@/api/ppt'
import { formatTime } from '@/utils/index'

export default {
  name: 'ppt',
  data() {
    return {
      tempArr: [],
      isHoverCanv: false,
      operList: [],
      operListMaxLen: 5,
      operPointer: 0,
      webFrame: null,
      svgArray: [],
      canvMenuShow: false,
      canvMenuOper: null,
      canvMenuX: 0,
      canvMenuY: 0,
      objMenuShow: false,
      objMenuOper: null,
      objMenuX: 0,
      objMenuY: 0,
      pptMenuOper: null,
      pptMenuShow: false,
      pptMenuX: 0,
      pptMenuY: 0,
      saveLoading: false,
      whichCanv: 0,
      canvList: [
        {
          version: '5.2.1',
          objects: [],
          background: '#FFFFFFFF'
        }
      ],
      stateOper: null,
      clipboard: null,
      ctrlKey: false,
      selectedCanvObjs: [],
      prevSelectedCanvObjs: [],
      tempCanvObjs: [],
      canvPanel: [0],
      canvBkColorMenu: false,
      canvBkColor: '#FFFFFFFF',
      canvImgUrl: '',
      canvTab: null,
      nowType: 'canvas',
      nowObj: null,
      textPanel: [0, 1, 2, 3, 4, 5],
      decorateWhich: null,
      textAlignWhich: null,
      newUrl: '',
      lineHeight: 12,
      charSpacing: 0,
      fontFamily: 'Times New Roman',
      fontFamilyArray: [
        'Times New Roman',
        '宋体',
        '楷体',
        '华文行楷',
        '隶书',
        '等线',
        '黑体',
        '微软雅黑',
        '方正姚体',
        '方正舒体',
        '幼圆',
        '华文彩云',
        'Arial',
        'Calibri',
        'Courier New',
        'Curlz MT',
        'Ebrima',
        'Edwardian Script ITC',
        'Fixedsys',
        'Forte',
        'Gabriola',
        'Georgia',
        'Pristina',
        'Ravie',
        'Tahoma',
        'Verdana',
        'Yu Gothic'
      ],
      fontSize: 48,
      fontSizeArray: [
        12, 14, 16, 18, 22, 24, 32, 48, 48, 64, 92, 112, 124, 148, 148, 162,
        178, 216, 246, 272, 312, 344, 372
      ],
      bkColor: '#00000000',
      bkColorMenu: false,
      objAngle: 0,
      objX: 0,
      objY: 0,
      objWidth: 200,
      objHeight: 200,
      isAspectRatio: false,
      opacity: 1,
      radiusX: 0,
      radiusY: 0,
      skewX: 0,
      skewY: 0,
      shadowColor: '#000000FF',
      shadowBlur: 0,
      shadowX: 0,
      shadowY: 0,
      shadowColorMenu: false,
      fillColorMenu: false,
      fillColor: '#FF0000FF',
      strokeColorMenu: false,
      strokeColor: '#FF0000',
      strokeWidth: 0,
      strokeType: '实线',
      leftTab: 0,
      rightTab: 0,
      stylePanel: [0, 1, 2, 3, 4],
      alignPanel: [0, 1, 2, 3, 4],
      alignWhich: null,
      placeWhich: null,
      imgMenu: false,
      imgUrl: '',
      imgUrlRules: [
        (v) => !!v || '请输入图片链接',
        (v) => /(http(s)?:\/\/\S+)/gi.test(v) || '图片链接必须符合规则'
      ],
      valid: true,
      urlMenu: false,
      webName: '',
      webUrl: '',
      webNameRules: [(v) => !!v || '请输入名称'],
      webUrlRules: [
        (v) => !!v || '请输入网址',
        (v) =>
          /http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w- ./?%&=]*)?/.test(v) ||
          '网址必须符合规则'
      ],
      operBtnColor: 'blue-grey lighten-4',
      expendIcon: 'zoom_out_map',
      drawerRight: true,
      scale: 16 / 9, // 画布宽高比例
      zoom: 1, // 画布当前缩放
      cw: 1280, // 画布实际宽
      ch: 720, // 画布实际高
      ow: 1920, // 画布比例宽
      oh: 1080, // 画布比例、高
      gx: 30, // 画布的x边距
      gy: 30, // 画布的y边距
      canv: null,
      padding: 30,
      elOper: null,
      elType: null,
      tempObj: null,
      tempLine: null,
      isDown: false,
      beginPoint: {
        x: 0,
        y: 0
      },
      endPoint: {
        x: 0,
        y: 0
      },
      seleObjs: [],
      pptId: 0,
      update_time: '',
      pptInfo: {
        tag: []
      },
      isPageActive: false
    }
  },
  // 离开本页面之前
  beforeRouteLeave(to, from, next) {
    if (!window.confirm('请确保数据已保存')) {
      this.$toast.warn('请尽快保存数据')
      return next(false) // 必须加上false，要不然route中的路由信息会发生变化
    }
    // 关闭加载层，会有800ms延迟
    this.$layer.hide()
    // 进入预览页面时本页面缓存
    if (to.name === 'movie') {
      this.$route.meta.keepAlive = true
    } else {
      this.$route.meta.keepAlive = false
    }
    // 保关闭存定时器
    if (this.loopStore) {
      clearInterval(this.loopStore)
    }
    // 关闭弹窗
    if (this.webFrame) {
      this.webFrame.close()
    }
    next()
  },
  destroyed() {
    // 关闭弹窗
    if (this.webFrame) {
      this.webFrame.close()
    }
  },
  beforeRouteEnter(to, from, next) {
    next((vm) => {
      // 重置页面大小
      if (from.name === 'movie') {
        setTimeout(() => {
          if ($(window).height() === window.screen.height) {
            vm.expendIcon = 'zoom_in_map'
          } else {
            vm.expendIcon = 'zoom_out_map'
          }
          vm.getSize()
          vm.resetSize()
          vm.resetPos()
        }, 300)
      }
    })
  },
  mounted() {
    this.$layer.show()
    this.pptId = this.$route.params.id
    getOnePPT({ id: this.pptId })
      .then((res) => {
        this.pptInfo.create_time = res.data.data.create_time
        this.pptInfo.update_time = res.data.data.update_time
        this.pptInfo.title = res.data.data.title
        this.pptInfo.intro = res.data.data.intro
        this.pptInfo.cover = res.data.data.cover
        this.pptInfo.tag = JSON.parse(res.data.data.tag)
        this.update_time = formatTime(this.pptInfo.update_time)
        this.canvList = JSON.parse(res.data.data.content)
        // this.canvList.forEach((item) => {
        //   item.objects.forEach((item) => {
        //     this.makeObjId(item)
        //   })
        // })
        this.$nextTick(() => {
          this.initCanv()
          // 初始化临时画布
          this.tcanv = new fabric.StaticCanvas('tcanv', {
            backgroundColor: 'white',
            width: this.ow,
            height: this.oh
          })
          this.tcanv.setZoom(1 / 12)
          this.tcanv.setWidth(this.ow / 12)
          this.tcanv.setHeight(this.oh / 12)

          this.canv.loadFromJSON(this.canvList[this.whichCanv], () => {
            // 初始化撤回队列
            this.recordOper()
          })
          this.canvBkColor = this.canv.backgroundColor
          // 这里是为了让程序不在主进程，避免卡顿
          setTimeout(() => {
            this.getSvgArray()
          })
          if (this.loopStore) {
            clearInterval(this.loopStore)
          }
          // 五分钟自动保存一次
          this.loopStore = setInterval(() => {
            this.storePPT()
          }, 300000)
        })
        this.mountEvent()
      })
      .catch(() => {
        if (confirm('请点击确定刷新重试')) {
          location.reload()
        }
      })
    // const ppt = localStorage.getItem('ppt')
    // if (ppt) {
    //   this.canvList = JSON.parse(ppt)
    // } else {
    //   console.log('从后端加载数据')
    // }
  },
  beforeDestroy() {
    // 本来打算自动保存，暂且关掉
    // if (this.isPageActive) {
    //   this.storePPT(false)
    // }
  },
  computed: {
    realTime() {
      return (time) => {
        return formatTime(time)
      }
    },
    canvObjs() {
      // 画布没有加载出来的话，返回空数组
      if (!this.canv) {
        return []
      }
      // 有类型也就是正在添加对象时，返回上一次的对象数组
      if (this.elType) {
        return this.tempCanvObjs
      }
      // 没有添加对象时，返回最新的对象数组
      return this.canv.getObjects()
    }
  },
  watch: {
    // 因为computed里面不允许更改值，所以在这里写
    canvObjs: {
      handler(nv) {
        this.tempCanvObjs = nv
      }
    }
  },
  methods: {
    recordOper() {
      console.log(this.operList)
      this.operList.push(this.canv.toJSON(['webUrl', 'id']))
      if (this.operList.length > this.operListMaxLen) {
        this.operList.shift()
      }
      this.operPointer = this.operList.length - 1
    },
    clearOper() {
      this.operList = []
    },
    gotoHome() {
      this.$router.go(-1)
    },
    openShowPPT() {
      this.$router.push({ name: 'movie', params: { id: this.pptId } })
    },
    openWebFrame() {
      setTimeout(() => {
        this.elOper = null
      }, 100)
      if (this.webFrame && !this.webFrame.closed) {
        this.webFrame.focus()
        return
      }
      const width = 1400
      const height = 780
      const left = (window.outerWidth - width) / 2
      const top = (window.outerHeight - height) / 2
      this.webFrame = window.open(
        'http://101.35.43.119/',
        'webFrame',
        `left=${left}, top=${top}, width=${width}, height=${height}`
      )
      // this.webFrame.document.title = '穿越门'
      // this.webFrame.document.body.innerHTML = `
      // <a href="https://www.baidu.com/">百度</a>
      // <a href="https://www.desmos.com/calculator?lang=zh-CN">图形计算器</a>
      // <a href="https://naotu.baidu.com/home">百度脑图</a>
      // <a href="https://app.diagrams.net/">draw io</a>
      // `
      this.webFrame.focus()
    },
    async getSvgArray() {
      if (!this.tcanv) {
        return
      }
      // 这里要用同步，因为图片加载会出现左侧预览问题
      for (let i = 0; i < this.canvList.length; i++) {
        await new Promise((resolve) => {
          this.tcanv.loadFromJSON(this.canvList[i], () => {
            resolve()
          })
        }).then(() => {
          this.$set(this.svgArray, i, this.getSvg())
        })
      }
      this.$layer.hide()
    },
    getSvg() {
      // return this.tcanv.toSVG({
      //   suppressPreamble: true,
      //   width: 160,
      //   height: 90
      // })
      return this.tcanv.toDataURL({
        format: 'jpeg',
        quality: 0.4,
        multiplier: 1
      })
    },
    // placeCenter(t) {
    //   const my = Math.max(
    //     t.aCoords.bl.y,
    //     t.aCoords.br.y,
    //     t.aCoords.tl.y,
    //     t.aCoords.tr.y
    //   )
    //   const sy = Math.min(
    //     t.aCoords.bl.y,
    //     t.aCoords.br.y,
    //     t.aCoords.tl.y,
    //     t.aCoords.tr.y
    //   )
    //   const mx = Math.max(
    //     t.aCoords.bl.x,
    //     t.aCoords.br.x,
    //     t.aCoords.tl.x,
    //     t.aCoords.tr.x
    //   )
    //   const sx = Math.min(
    //     t.aCoords.bl.x,
    //     t.aCoords.br.x,
    //     t.aCoords.tl.x,
    //     t.aCoords.tr.x
    //   )
    //   t.set('left', this.ow / 2 - ((mx + sx) / 2 - t.left)).setCoords()
    //   t.set('top', this.oh / 2 - ((my + sy) / 2 - t.top)).setCoords()
    //   this.objX = t.left - (t.left - sx)
    //   this.objY = t.top - (t.top - sy)
    //   this.canv.renderAll()
    // },
    chooseCanvMenuOper() {
      this.$nextTick(() => {
        this.canvMenuOper = null
      })
      if (this.canvMenuOper === 0) {
        const t = this.canv.getActiveObject()
        if (!t || (t && t.get('type') === 'textbox' && t.isEditing)) {
          return
        }
        t.clone((cloned) => {
          if (t.webUrl) {
            cloned.webUrl = t.webUrl
          }
          this.clipboard = cloned
        })
      } else if (this.canvMenuOper === 1) {
        const t = this.canv.getActiveObject()
        if (!t || (t && t.get('type') === 'textbox' && t.isEditing)) {
          return
        }
        t.clone((cloned) => {
          if (t.webUrl) {
            cloned.webUrl = t.webUrl
          }
          this.clipboard = cloned
        })
        this.canv.remove(t)
      } else if (this.canvMenuOper === 2) {
        if (!this.clipboard) {
          return
        }
        const t = this.canv.getActiveObject()
        if (t && t.type === 'textbox' && t.isEditing) {
          this.clipboard = null
          return
        }
        this.clipboard.clone((clonedObj) => {
          this.canv.discardActiveObject()
          clonedObj.set({
            left: clonedObj.left + 10,
            top: clonedObj.top + 10,
            evented: true
          })
          if (clonedObj.type === 'activeSelection') {
            clonedObj.canvas = this.canv
            clonedObj.forEachObject((obj) => {
              this.canv.add(obj)
            })
            clonedObj.setCoords()
          } else {
            if (this.clipboard.webUrl) {
              clonedObj.webUrl = this.clipboard.webUrl
            }
            this.canv.add(clonedObj)
          }
          this.clipboard.top += 10
          this.clipboard.left += 10
          this.canv.setActiveObject(clonedObj)
          this.canv.requestRenderAll()
          this.clipboard = null
        })
      } else if (this.canvMenuOper === 3) {
        const t = this.canv.getActiveObject()
        if (!t) {
          return
        }
        this.canv.remove(t)
        this.canv.renderAll()
      } else if (this.canvMenuOper === 4) {
        const t = this.canv.getActiveObject()
        if (!t) {
          return
        }
        t.bringForward()
        this.canv.setActiveObject(t)
        this.canv.renderAll()
      } else if (this.canvMenuOper === 5) {
        const t = this.canv.getActiveObject()
        if (!t) {
          return
        }
        t.sendBackwards()
        this.canv.setActiveObject(t)
        this.canv.renderAll()
      } else if (this.canvMenuOper === 6) {
        const t = this.canv.getActiveObject()
        if (!t) {
          return
        }
        t.bringToFront()
        this.canv.setActiveObject(t)
        this.canv.renderAll()
      } else if (this.canvMenuOper === 7) {
        const t = this.canv.getActiveObject()
        if (!t) {
          return
        }
        t.sendToBack()
        this.canv.setActiveObject(t)
        this.canv.renderAll()
      }
    },
    chooseObjMenuOper() {
      this.$nextTick(() => {
        this.objMenuOper = null
      })
      if (this.objMenuOper === 0) {
        const acT = this.canv.getActiveObject()
        if (acT && acT.get('type') === 'textbox' && acT.isEditing) {
          return
        }
        const t = this.canvObjs[this.tempObjIndex]
        t.clone((cloned) => {
          if (t.webUrl) {
            cloned.webUrl = t.webUrl
          }
          this.clipboard = cloned
        })
      } else if (this.objMenuOper === 1) {
        const acT = this.canv.getActiveObject()
        if (acT && acT.get('type') === 'textbox' && acT.isEditing) {
          return
        }
        const t = this.canvObjs[this.tempObjIndex]
        t.clone((cloned) => {
          if (t.webUrl) {
            cloned.webUrl = t.webUrl
          }
          this.clipboard = cloned
        })
        this.canv.remove(t)
      } else if (this.objMenuOper === 2) {
        if (!this.clipboard) {
          return
        }
        const t = this.canv.getActiveObject()
        if (t && t.type === 'textbox' && t.isEditing) {
          this.clipboard = null
          return
        }
        this.clipboard.clone((clonedObj) => {
          this.canv.discardActiveObject()
          clonedObj.set({
            left: clonedObj.left + 10,
            top: clonedObj.top + 10,
            evented: true
          })
          if (clonedObj.type === 'activeSelection') {
            clonedObj.canvas = this.canv
            clonedObj.forEachObject((obj) => {
              this.canv.add(obj)
            })
            clonedObj.setCoords()
          } else {
            if (this.clipboard.webUrl) {
              clonedObj.webUrl = this.clipboard.webUrl
            }
            this.canv.add(clonedObj)
          }
          this.clipboard.top += 10
          this.clipboard.left += 10
          this.canv.setActiveObject(clonedObj)
          this.canv.requestRenderAll()
          this.clipboard = null
        })
      } else if (this.objMenuOper === 3) {
        this.canv.remove(this.canvObjs[this.tempObjIndex])
        this.canv.renderAll()
      } else if (this.objMenuOper === 4) {
        const tempObj = this.canvObjs[this.tempObjIndex]
        tempObj.bringForward()
        this.canv.setActiveObject(tempObj)
        this.canv.renderAll()
      } else if (this.objMenuOper === 5) {
        const tempObj = this.canvObjs[this.tempObjIndex]
        tempObj.sendBackwards()
        this.canv.setActiveObject(tempObj)
        this.canv.renderAll()
      } else if (this.objMenuOper === 6) {
        const tempObj = this.canvObjs[this.tempObjIndex]
        tempObj.bringToFront()
        this.canv.setActiveObject(tempObj)
        this.canv.renderAll()
      } else if (this.objMenuOper === 7) {
        const tempObj = this.canvObjs[this.tempObjIndex]
        tempObj.sendToBack()
        this.canv.setActiveObject(tempObj)
        this.canv.renderAll()
      }
    },
    choosePPTMenuOper() {
      this.$nextTick(() => {
        this.pptMenuOper = null
      })
      if (this.pptMenuOper === 0) {
        const newPage = {
          version: '5.2.1',
          objects: [],
          background: '#FFFFFFFF'
        }
        // 新建一个空白画布对象
        this.canvList.splice(this.whichCanv + 1, 0, newPage)
        // 更新左侧页面
        this.tcanv.loadFromJSON(newPage, () => {
          this.svgArray.splice(this.whichCanv + 1, 0, this.getSvg())
        })
        this.changePPT(this.whichCanv + 1)
      } else if (this.pptMenuOper === 1) {
        if (this.canvList.length === 1) {
          this.$toast.warn('仅剩一页无法删除')
          return
        }
        // 删除对应的画布对象
        this.canvList.splice(this.whichCanv, 1)
        // 删除左侧临时页面
        this.svgArray.splice(this.whichCanv, 1)
        this.whichCanv = Math.max(this.whichCanv - 1, 0)
        this.loadNowPPT()
      } else if (this.pptMenuOper === 2) {
        this.tempClonedPPT = this.canvList[this.whichCanv]
      } else if (this.pptMenuOper === 3) {
        this.tempClonedPPT = this.canvList[this.whichCanv]
        // 删除对应画布对象
        this.canvList.splice(this.whichCanv, 1)
        // 删除左侧临时页面
        this.svgArray.splice(this.whichCanv, 1)
        this.whichCanv = Math.max(this.whichCanv - 1, 0)
        this.loadNowPPT()
      } else if (this.pptMenuOper === 4) {
        if (!this.tempClonedPPT) {
          return
        }
        // 插入对象的画布对象
        this.canvList.splice(this.whichCanv + 1, 0, this.tempClonedPPT)
        // 更新左侧临时页面
        this.tcanv.loadFromJSON(this.tempClonedPPT, () => {
          this.svgArray.splice(this.whichCanv + 1, 0, this.getSvg())
        })
        // 清空克隆的画布对象
        this.tempClonedPPT = null
        this.changePPT(this.whichCanv + 1)
      }
    },
    showCanvMenu(e) {
      e.preventDefault()
      const t = this.canv.getActiveObject()
      if (t && t.get('type') === 'textbox' && t.isEditing) {
        return
      }
      this.canvMenuShow = false
      this.canvMenuX = e.clientX
      this.canvMenuY = e.clientY
      this.$nextTick(() => {
        this.canvMenuShow = true
      })
    },
    showPPTMenu(e, index) {
      e.preventDefault()
      this.pptMenuShow = false
      this.pptMenuX = e.clientX
      this.pptMenuY = e.clientY
      this.changePPT(index)
      this.$nextTick(() => {
        this.pptMenuShow = true
      })
    },
    showObjMenu(e, index) {
      e.preventDefault()
      this.objMenuShow = false
      this.objMenuX = e.clientX
      this.objMenuY = e.clientY
      this.tempObjIndex = index
      this.$nextTick(() => {
        this.objMenuShow = true
      })
    },
    loadNowPPT() {
      this.canv.loadFromJSON(this.canvList[this.whichCanv])
      this.canvBkColor = this.canvList[this.whichCanv].background || '#FFFFFFFF'
      this.clearOper()
      this.recordOper()
    },
    changePPT(index) {
      if (index === undefined || index === null) {
        return
      }
      // 先保存上一页的画布
      this.canvList[this.whichCanv] = this.canv.toJSON(['webUrl', 'id'])

      // 更新左侧页面，这里为了异步函数执行完成后使用私有变量
      const getPrevPage = () => {
        const that = this
        const whichCanv = that.whichCanv
        return () => {
          that.tcanv.loadFromJSON(that.canvList[whichCanv], () => {
            that.$set(that.svgArray, whichCanv, that.getSvg())
              that.tempArr.shift()
              if (that.tempArr.length > 0) {
                that.tempArr[0]()
              }
          })     
        }
      }
      // 这里用到了自行实现的异步队列
      this.tempArr.push(getPrevPage())
      if (this.tempArr.length === 1) {
        this.tempArr[0]()
      }

      // 加载当前页面
      this.whichCanv = index
      this.loadNowPPT()
    },
    addPPT() {
      const newPage = {
        version: '5.2.1',
        objects: [],
        background: '#FFFFFFFF'
      }
      this.canvList.push(newPage)
      // 更新左侧页面
      this.tcanv.loadFromJSON(newPage, () => {
        this.$set(this.svgArray, this.svgArray.length, this.getSvg())
      })
      // 更新当前页面
      this.changePPT(this.canvList.length - 1)
    },
    storePPT(isTip = true) {
      if (this.timeout || this.isStoring) {
        return
      }
      this.isStoring = true
      // 转换加载图标
      this.saveLoading = true
      this.timeout = setTimeout(() => {
        this.saveLoading = false
        clearTimeout(this.timeout)
        this.timeout = null
      }, 900 + Math.floor(Math.random() * 300))
      // 更新画布对象数组
      this.canvList[this.whichCanv] = this.canv.toJSON(['webUrl', 'id'])
      // 更新左侧页面
      const that = this
      ;(function (whichCanv) {
        that.tcanv.loadFromJSON(that.canvList[whichCanv], () => {
          that.$set(that.svgArray, whichCanv, that.getSvg())
        })
      })(this.whichCanv)
      updatePPT({ id: this.pptId, content: JSON.stringify(this.canvList) })
        .then((res) => {
          this.update_time = res.data.data.update_time
          if (isTip) {
            this.$toast.success('保存成功')
          }
        })
        .catch(() => {})
        .complete(() => {
          this.isStoring = false
        })
      // localStorage.setItem('ppt', JSON.stringify(this.canvList))
    },
    stateOperChange() {
      this.$nextTick(() => {
        this.stateOper = null
      })
      if (this.stateOper === 0) {
        this.storePPT()
      } else if (this.stateOper === 1) {
        this.undoOper()
      } else if (this.stateOper === 2) {
        this.restoreOper()
      }
    },
    undoOper() {
      if (this.operPointer === 0) {
        this.$toast.warn('已撤回到尽头')
        return
      }
      this.operPointer--
      this.canv.loadFromJSON(this.operList[this.operPointer])
      this.canvBkColor =
        this.operList[this.operPointer].background || '#FFFFFFFF'
    },
    restoreOper() {
      if (this.operPointer >= this.operList.length - 1) {
        this.$toast.warn('已恢复到尽头')
        return
      }
      this.operPointer++
      this.canv.loadFromJSON(this.operList[this.operPointer])
      this.canvBkColor =
        this.operList[this.operPointer].background || '#FFFFFFFF'
    },
    changeSelectedCanvObjs() {
      if (this.selectedCanvObjs.length === 0) {
        this.prevSelectedCanvObjs = this.selectedCanvObjs
        this.canv.discardActiveObject().renderAll()
        return
      }
      // if (this.ctrlKey) {
      //   const selObjs = []
      //   this.canvObjs.forEach((item, index) => {
      //     if (this.selectedCanvObjs.includes(index)) {
      //       selObjs.push(item)
      //     }
      //   })
      //   // 移除画布中的对象
      //   selObjs.forEach(item => {
      //     this.canv.remove(item)
      //   })
      //   console.log(selObjs)
      //   // 构造组合
      //   const group = new fabric.Group(selObjs)
      //   this.canv.add(group)
      //   this.canv.setActiveObject(group)
      //   // 将组合拆分
      //    this.canv.getActiveObject().toActiveSelection()
      //   const groupArr = group.getObjects()
      //   console.log(this.canv.getObjects())
      //   const idArr = this.canvObjs.map(item => item.id)
      //   const indexObj = {}
      //   selObjs.forEach((item, index) => {
      //     indexObj[item.id] = this.selectedCanvObjs[index]
      //   })
      //   groupArr.forEach(item => {
      //     const diff = idArr.indexOf(item.id) - indexObj[item.id]
      //     for (let i = 0; i < diff; i++) {
      //       this.canv.sendBackwards(item)
      //     }
      //   })
      //   this.canv.renderAll()
      //   return
      // }
      const prevSum =
        this.prevSelectedCanvObjs.length !== 0
          ? this.prevSelectedCanvObjs.reduce((prev, current) => {
              return prev + current
            })
          : 0
      const nowSum =
        this.selectedCanvObjs.length !== 0
          ? this.selectedCanvObjs.reduce((prev, current) => {
              return prev + current
            })
          : 0
      const index = Math.abs(nowSum - prevSum)
      this.selectedCanvObjs = [index]
      this.canv.discardActiveObject()
      this.canv.setActiveObject(this.canvObjs[index]).renderAll()
      this.prevSelectedCanvObjs = this.selectedCanvObjs
    },
    resetTab() {
      if (this.nowType) {
        if (this.nowType === 'textbox') {
          return
        } else {
          this.rightTab = 0
        }
        return
      }
      if (this.elType) {
        if (this.elType === 'text') {
          return
        } else {
          this.rightTab = 0
        }
      }
    },
    makeObjId(obj) {
      obj.id =
        Math.floor(Math.random() * 10000000).toString() +
        '-' +
        Date.now().toString()
    },
    changeCanvTab() {
      setTimeout(() => {
        this.canvTab = null
      })
    },
    changeCanvBkColor() {
      if (this.nowType !== 'canvas') {
        return
      }
      this.canv.backgroundImage = 0
      this.canv.setBackgroundColor(
        this.canvBkColor || '#FFFFFFFF',
        this.canv.renderAll.bind(this.canv)
      )
      this.canv.renderAll()
    },
    getActiveData(e) {
      let selObjs = this.canv.getActiveObjects()
      if (e) {
        selObjs = e.selected
      }
      if (selObjs.length !== 1) {
        this.nowType = 'canvas'
        return
      }
      this.nowType = selObjs[0].get('type')
      this.nowObj = selObjs[0]
      // 编辑对象参数赋值
      const t = selObjs[0]
      const sy = Math.min(
        t.aCoords.bl.y,
        t.aCoords.br.y,
        t.aCoords.tl.y,
        t.aCoords.tr.y
      )
      const sx = Math.min(
        t.aCoords.bl.x,
        t.aCoords.br.x,
        t.aCoords.tl.x,
        t.aCoords.tr.x
      )
      this.objX = Math.round(t.left - (t.left - sx))
      this.objY = Math.round(t.top - (t.top - sy))
      this.objAngle = Math.round(selObjs[0].angle || 0)
      this.opacity =
        selObjs[0].opacity === null || selObjs[0].opacity === undefined
          ? 1
          : selObjs[0].opacity
      if (this.nowType === 'textbox') {
        const t = this.canv.getActiveObject()
        const ed = t.selectionEnd || 1
        const s = t.getSelectionStyles(ed - 1, ed, true)[0]
        this.fillColor = s.fill || '#000000FF'
        this.bkColor = s.textBackgroundColor || '#00000000'
        this.fontFamily = s.fontFamily || 'Times New Roman'
        this.fontSize = s.fontSize || 48
        this.lineHeight = t.get('lineHeight') * 10 || 0
        this.charSpacing = t.get('charSpacing') / 100 || 0
        this.strokeColor = s.stroke || '#000000'
        this.strokeWidth = s.strokeWidth || 0
        if (this.nowObj && this.nowObj.webUrl) {
          this.newUrl = this.nowObj.webUrl
        }
      } else {
        this.strokeColor = selObjs[0].stroke || '#000000'
        this.strokeWidth = selObjs[0].strokeWidth || 0
      }
      if (!['line', 'path', 'textbox'].includes(this.nowType)) {
        // 对象宽高
        this.objWidth = Math.round(
          selObjs[0].width * selObjs[0].scaleX - selObjs[0].strokeWidth || 200
        )
        this.objHeight = Math.round(
          selObjs[0].height * selObjs[0].scaleY - selObjs[0].strokeWidth || 200
        )
      }
      if (!['image', 'line', 'path'].includes(this.nowType)) {
        // 填充颜色
        if (this.nowType === 'textbox') {
          this.fillColor = selObjs[0].getCurrentCharColor()
            ? selObjs[0].getCurrentCharColor()
            : '#000000FF'
        } else {
          this.fillColor = selObjs[0].fill ? selObjs[0].fill : '#BBBBBBFF'
        }
      }
      if (this.nowType === 'rect') {
        // 圆角
        this.radiusX = Math.round(selObjs[0].rx * selObjs[0].get('scaleX') || 0)
        this.radiusY = Math.round(selObjs[0].ry * selObjs[0].get('scaleY') || 0)
      }
      if (this.nowType === 'rect' || this.nowType === 'triangle') {
        this.skewX = selObjs[0].skewX || 0
        this.skewY = selObjs[0].skewY || 0
      }
      if (this.nowType === 'rect' || this.nowType === 'line') {
        // 边框类型
        this.strokeType = selObjs[0].strokeDashArray ? '虚线' : '实线'
      }
      if (['rect', 'circle', 'triangle', 'textbox'].includes(this.nowType)) {
        // 阴影
        this.shadowColor = selObjs[0].shadow.color || '#000000FF'
        this.shadowBlur = selObjs[0].shadow.blur || 0
        this.shadowX = selObjs[0].shadow.offsetX || 0
        this.shadowY = selObjs[0].shadow.offsetY || 0
      }
    },
    getActiveIndex(e) {
      if (this.leftTab !== 1) {
        return
      }
      let selObjs = this.canv.getActiveObjects()
      if (e) {
        selObjs = e.selected
      }
      // 因为selection:create等事件早于canvObjs的更新，所以这里直接获取所有对象
      const all = this.canv.getObjects()
      const ids = selObjs.map((item) => item.id)
      const selArr = []
      for (let i = 0; i < all.length; i++) {
        for (let j = 0; j < ids.length; j++) {
          if (all[i].id === ids[j]) {
            selArr.push(i)
            break
          }
        }
      }
      this.selectedCanvObjs = selArr
      this.prevSelectedCanvObjs = selArr
    },
    handleTextAlign() {
      // 更改层级后立刻去除标记
      setTimeout(() => {
        this.textAlignWhich = null
      })
      if (!this.canv.getActiveObject()) {
        return
      }
      const t = this.canv.getActiveObject()
      if (this.textAlignWhich === 0) {
        t.set('textAlign', 'left')
      } else if (this.textAlignWhich === 1) {
        t.set('textAlign', 'center')
      } else if (this.textAlignWhich === 2) {
        t.set('textAlign', 'right')
      }
      this.canv.renderAll()
    },
    handleDecorate() {
      // 更改层级后立刻去除标记
      setTimeout(() => {
        this.decorateWhich = null
      })
      if (!this.canv.getActiveObject()) {
        return
      }
      const t = this.canv.getActiveObject()
      const ed = t.selectionEnd
      const s = t.getSelectionStyles(
        ed - 1 === -1 ? 0 : ed - 1,
        ed === 0 ? 1 : ed,
        true
      )[0]
      if (this.decorateWhich === 0) {
        if (s.fontWeight !== 'bold') {
          t.setSelectionStyles(
            {
              fontWeight: 'bold'
            },
            t.selectionStart,
            t.selectionEnd
          )
        } else {
          t.setSelectionStyles(
            {
              fontWeight: 'normal'
            },
            t.selectionStart,
            t.selectionEnd
          )
        }
      } else if (this.decorateWhich === 1) {
        if (s.fontStyle !== 'italic') {
          t.setSelectionStyles(
            {
              fontStyle: 'italic'
            },
            t.selectionStart,
            t.selectionEnd
          )
        } else {
          t.setSelectionStyles(
            {
              fontStyle: 'normal'
            },
            t.selectionStart,
            t.selectionEnd
          )
        }
      } else if (this.decorateWhich === 2) {
        if (!s.underline) {
          t.setSelectionStyles(
            {
              underline: true
            },
            t.selectionStart,
            t.selectionEnd
          )
        } else {
          t.setSelectionStyles(
            {
              underline: false
            },
            t.selectionStart,
            t.selectionEnd
          )
        }
      } else if (this.decorateWhich === 3) {
        if (!s.linethrough) {
          t.setSelectionStyles(
            {
              linethrough: true
            },
            t.selectionStart,
            t.selectionEnd
          )
        } else {
          t.setSelectionStyles(
            {
              linethrough: false
            },
            t.selectionStart,
            t.selectionEnd
          )
        }
      } else if (this.decorateWhich === 4) {
        if (s.deltaY === 0) {
          t.setSuperscript(t.selectionStart, t.selectionEnd)
        } else {
          // 用于取消上标或下标
          t.setSelectionStyles(
            {
              fontSize: undefined,
              deltaY: undefined
            },
            t.selectionStart,
            t.selectionEnd
          )
        }
      } else if (this.decorateWhich === 5) {
        if (s.deltaY === 0) {
          t.setSubscript(t.selectionStart, t.selectionEnd)
        } else {
          t.setSelectionStyles(
            {
              fontSize: undefined,
              deltaY: undefined
            },
            t.selectionStart,
            t.selectionEnd
          )
        }
      }
      this.canv.renderAll()
    },
    changeNewUrl() {
      if (!this.canv.getActiveObject()) {
        return
      }
      if (this.nowType !== 'textbox' || !this.canv.getActiveObject().webUrl) {
        return
      }
      if (!this.$refs.newUrlForm.validate()) {
        return
      }
      this.canv.getActiveObject().webUrl = this.newUrl
      this.canv.renderAll()
    },
    changeCharSpacing() {
      if (!this.canv.getActiveObject()) {
        return
      }
      if (this.nowType !== 'textbox') {
        return
      }
      const t = this.canv.getActiveObject()
      // 这里的文字间距基本单位是em/1000，太小了，所以我设置成em/10
      t.set('charSpacing', parseInt(this.charSpacing) * 100 || 0)
      this.canv.renderAll()
    },
    changeLineHeight() {
      if (!this.canv.getActiveObject()) {
        return
      }
      if (this.nowType !== 'textbox') {
        return
      }
      const t = this.canv.getActiveObject()
      t.set('lineHeight', parseInt(this.lineHeight) / 10 || 0)
      this.canv.renderAll()
    },
    changeFontSize() {
      if (!this.canv.getActiveObject()) {
        return
      }
      if (this.nowType !== 'textbox') {
        return
      }
      const t = this.canv.getActiveObject()
      t.setSelectionStyles(
        {
          fontSize: parseInt(this.fontSize) || 0
        },
        t.selectionStart,
        t.selectionEnd
      ).setCoords()
      // 这里是为了设置整体的fontSize，charSpacing的渲染是依靠整体fontSize来计算的，
      // 所以每次改变字体大小，获取所有文字样式中最小的字体大小赋给整体fontSize
      const originStylesArray = t.getSelectionStyles(0, t.text.length, true)
      const originFontSizeArray = originStylesArray.map(
        (item) => item.fontSize || 48
      )
      t.set('fontSize', Math.min(...originFontSizeArray))
      originStylesArray.forEach((item, index) => {
        t.setSelectionStyles(
          {
            fontSize: item.fontSize
          },
          index,
          index + 1
        )
      })
      const st = t.selectionStart
      const ed = t.selectionEnd
      this.canv.discardActiveObject(t).setActiveObject(t)
      t.setSelectionStart(st)
      t.setSelectionEnd(ed)
      this.canv.renderAll()
    },
    changeFontFamily() {
      if (!this.canv.getActiveObject()) {
        return
      }
      if (this.nowType !== 'textbox') {
        return
      }
      const t = this.canv.getActiveObject()
      t.setSelectionStyles(
        {
          fontFamily: this.fontFamily || 'Times New Roman'
        },
        t.selectionStart,
        t.selectionEnd
      )
      this.canv.renderAll()
    },
    changeBkColor() {
      if (!this.canv.getActiveObject()) {
        return
      }
      if (this.nowType !== 'textbox') {
        return
      }
      const t = this.canv.getActiveObject()
      t.setSelectionStyles(
        {
          textBackgroundColor: this.bkColor || '#00000000'
        },
        t.selectionStart,
        t.selectionEnd
      )
      this.canv.renderAll()
    },
    changeObjAngle() {
      if (!this.canv.getActiveObject()) {
        return
      }
      this.canv
        .getActiveObject()
        .rotate(parseInt(this.objAngle || 0))
        .setCoords()
      this.canv.renderAll()
    },
    changeObjX() {
      if (!this.canv.getActiveObject()) {
        return
      }
      const t = this.canv.getActiveObject()
      const sx = Math.min(
        t.aCoords.bl.x,
        t.aCoords.br.x,
        t.aCoords.tl.x,
        t.aCoords.tr.x
      )
      const left = t.left - sx + parseInt(this.objX || 0)
      t.set('left', left).setCoords()
      this.canv.renderAll()
    },
    changeObjY() {
      if (!this.canv.getActiveObject()) {
        return
      }
      const t = this.canv.getActiveObject()
      const sy = Math.min(
        t.aCoords.bl.y,
        t.aCoords.br.y,
        t.aCoords.tl.y,
        t.aCoords.tr.y
      )
      const top = t.top - sy + parseInt(this.objY || 0)
      t.set('top', top).setCoords()
      this.canv.renderAll()
    },
    changeObjWidth() {
      if (!this.canv.getActiveObject()) {
        return
      }
      if (['line', 'path', 'textbox'].includes(this.nowType)) {
        return
      }
      const obj = this.canv.getActiveObject()
      const scaleX = (parseInt(this.objWidth) + obj.strokeWidth) / obj.width
      if (this.isAspectRatio) {
        if (obj.scaleX === obj.scaleY) {
          obj.set('scaleX', scaleX || 0).setCoords()
          obj.set('scaleY', scaleX || 0).setCoords()
          this.objHeight = Math.round(this.objWidth)
        } else {
          const scale = scaleX / obj.scaleX
          const scaleY = obj.scaleY * scale
          obj.set('scaleX', scaleX || 0).setCoords()
          obj.set('scaleY', scaleY || 0).setCoords()
          this.objHeight = Math.round(
            obj.height * obj.scaleY - obj.strokeWidth || 200
          )
        }
      } else {
        obj.set('scaleX', scaleX || 0).setCoords()
      }
      this.canv.renderAll()
    },
    changeObjHeight() {
      if (!this.canv.getActiveObject()) {
        return
      }
      if (['line', 'path', 'textbox'].includes(this.nowType)) {
        return
      }
      const obj = this.canv.getActiveObject()
      const scaleY = (parseInt(this.objHeight) + obj.strokeWidth) / obj.height
      if (this.isAspectRatio) {
        const scale = scaleY / obj.scaleY
        const scaleX = obj.scaleX * scale
        obj.set('scaleX', scaleX || 0).setCoords()
        obj.set('scaleY', scaleY || 0).setCoords()
        this.objWidth = obj.width * obj.scaleX - obj.strokeWidth || 200
      } else {
        obj.set('scaleY', scaleY || 0).setCoords()
      }
      this.canv.renderAll()
    },
    changeOpacity() {
      if (!this.canv.getActiveObject()) {
        return
      }
      this.canv.getActiveObject().opacity =
        this.opacity === null || this.opacity === undefined ? 1 : this.opacity
      this.canv.renderAll()
    },
    changeShadowColor() {
      if (!this.canv.getActiveObject()) {
        return
      }
      if (!['rect', 'circle', 'triangle', 'textbox'].includes(this.nowType)) {
        return
      }
      this.canv.getActiveObject().shadow.color = this.shadowColor
      this.canv.renderAll()
    },
    changeShadowBlur() {
      if (!this.canv.getActiveObject()) {
        return
      }
      if (!['rect', 'circle', 'triangle', 'textbox'].includes(this.nowType)) {
        return
      }
      this.canv.getActiveObject().shadow.blur = parseInt(this.shadowBlur) || 0
      this.canv.renderAll()
    },
    changeShadowX() {
      if (!this.canv.getActiveObject()) {
        return
      }
      if (!['rect', 'circle', 'triangle', 'textbox'].includes(this.nowType)) {
        return
      }
      this.canv.getActiveObject().shadow.offsetX = parseInt(this.shadowX) || 0
      this.canv.renderAll()
    },
    changeShadowY() {
      if (!this.canv.getActiveObject()) {
        return
      }
      if (!['rect', 'circle', 'triangle', 'textbox'].includes(this.nowType)) {
        return
      }
      this.canv.getActiveObject().shadow.offsetY = parseInt(this.shadowY) || 0
      this.canv.renderAll()
    },
    changeRadiusX() {
      if (!this.canv.getActiveObject()) {
        return
      }
      if (this.nowType !== 'rect') {
        return
      }
      const t = this.canv.getActiveObject()
      t.set('rx', parseInt(this.radiusX) / t.get('scaleX') || 0)
      this.canv.renderAll()
    },
    changeRadiusY() {
      if (!this.canv.getActiveObject()) {
        return
      }
      if (this.nowType !== 'rect') {
        return
      }
      const t = this.canv.getActiveObject()
      t.set('ry', parseInt(this.radiusY) / t.get('scaleY') || 0)
      this.canv.renderAll()
    },
    changeSkewX() {
      if (!this.canv.getActiveObject()) {
        return
      }
      if (this.nowType !== 'rect' && this.nowType !== 'triangle') {
        return
      }
      const t = this.canv.getActiveObject()
      t.set('skewX', parseInt(this.skewX) || 0).setCoords()
      this.canv.renderAll()
    },
    changeSkewY() {
      if (!this.canv.getActiveObject()) {
        return
      }
      if (this.nowType !== 'rect' && this.nowType !== 'triangle') {
        return
      }
      const t = this.canv.getActiveObject()
      t.set('skewY', parseInt(this.skewY) || 0).setCoords()
      this.canv.renderAll()
    },
    changeFillColor() {
      if (!this.canv.getActiveObject()) {
        return
      }
      if (['image', 'line', 'path'].includes(this.nowType)) {
        return
      }
      if (this.nowType === 'textbox') {
        const t = this.canv.getActiveObject()
        t.setSelectionStyles(
          {
            fill: this.fillColor || '#000000FF'
          },
          t.selectionStart,
          t.selectionEnd
        )
      } else {
        this.canv.getActiveObject().set('fill', this.fillColor)
      }
      this.canv.renderAll()
    },
    changeStrokeColor() {
      if (!this.canv.getActiveObject()) {
        return
      }
      if (this.nowType === 'textbox') {
        const t = this.canv.getActiveObject()
        t.setSelectionStyles(
          {
            stroke: this.strokeColor.slice(0, 7) || '#000000'
          },
          t.selectionStart,
          t.selectionEnd
        )
        this.canv.renderAll()
        return
      }
      this.canv.getActiveObject().set('stroke', this.strokeColor.slice(0, 7))
      this.canv.renderAll()
    },
    changeStrokeWidth() {
      if (!this.canv.getActiveObject()) {
        return
      }
      if (this.nowType === 'textbox') {
        const t = this.canv.getActiveObject()
        t.setSelectionStyles(
          {
            strokeWidth: parseInt(this.strokeWidth) || 0
          },
          t.selectionStart,
          t.selectionEnd
        )
        this.canv.renderAll()
        return
      }
      const obj = this.canv.getActiveObject()
      obj.set('strokeWidth', parseInt(this.strokeWidth) || 0).setCoords()
      if (!['line', 'path'].includes(this.nowType)) {
        this.objWidth = Math.round(
          obj.width * obj.scaleX - obj.strokeWidth || 200
        )
        this.objHeight = Math.round(
          obj.height * obj.scaleY - obj.strokeWidth || 200
        )
      }
      this.canv.renderAll()
    },
    changeStrokeType() {
      if (!this.canv.getActiveObject()) {
        return
      }
      if (this.nowType !== 'rect' && this.nowType !== 'line') {
        return
      }
      if (this.strokeType !== '实线') {
        this.canv.getActiveObject().set('strokeDashArray', [8, 8])
      } else {
        this.canv.getActiveObject().set('strokeDashArray', null)
      }
      this.canv.renderAll()
    },
    handlerAlign() {
      // 对齐后立刻去除标记
      setTimeout(() => {
        this.alignWhich = null
      })
      if (!this.canv.getActiveObject()) {
        return
      }
      const t = this.canv.getActiveObject()
      const my = Math.max(
        t.aCoords.bl.y,
        t.aCoords.br.y,
        t.aCoords.tl.y,
        t.aCoords.tr.y
      )
      const sy = Math.min(
        t.aCoords.bl.y,
        t.aCoords.br.y,
        t.aCoords.tl.y,
        t.aCoords.tr.y
      )
      const mx = Math.max(
        t.aCoords.bl.x,
        t.aCoords.br.x,
        t.aCoords.tl.x,
        t.aCoords.tr.x
      )
      const sx = Math.min(
        t.aCoords.bl.x,
        t.aCoords.br.x,
        t.aCoords.tl.x,
        t.aCoords.tr.x
      )
      if (this.alignWhich === 0) {
        t.set('left', t.left - sx).setCoords()
      } else if (this.alignWhich === 1) {
        t.set('left', this.ow / 2 - ((mx + sx) / 2 - t.left)).setCoords()
      } else if (this.alignWhich === 2) {
        t.set('left', this.ow - (mx - t.left)).setCoords()
      } else if (this.alignWhich === 3) {
        t.set('left', this.ow / 2 - ((mx + sx) / 2 - t.left)).setCoords()
        t.set('top', this.oh / 2 - ((my + sy) / 2 - t.top)).setCoords()
      } else if (this.alignWhich === 4) {
        t.set('top', t.top - sy).setCoords()
      } else if (this.alignWhich === 5) {
        t.set('top', this.oh / 2 - ((my + sy) / 2 - t.top)).setCoords()
      } else if (this.alignWhich === 6) {
        t.set('top', this.oh - (my - t.top)).setCoords()
      }
      this.objX = t.left - (t.left - sx)
      this.objY = t.top - (t.top - sy)
      this.canv.renderAll()
    },
    handlerPlace() {
      // 更改层级后立刻去除标记
      setTimeout(() => {
        this.placeWhich = null
      })
      if (!this.canv.getActiveObject()) {
        return
      }
      if (this.placeWhich === 0) {
        this.canv.getActiveObject().bringForward()
      } else if (this.placeWhich === 1) {
        this.canv.getActiveObject().sendBackwards()
      } else if (this.placeWhich === 2) {
        this.canv.getActiveObject().bringToFront()
      } else if (this.placeWhich === 3) {
        this.canv.getActiveObject().sendToBack()
      }
      this.canv.renderAll()
    },
    // 页面大小改变时获取画布应变为的大小
    getSize() {
      const tw = $('#canvWrap').width()
      const th = $('#canvWrap').height()
      const scale = tw / th
      // 非常长
      if (scale >= this.scale) {
        this.ch = th - this.gy * 2
        this.cw = this.ch * this.scale
      } else {
        this.cw = tw - this.gx * 2
        this.ch = this.cw / this.scale
      }
      this.zoom = this.cw / this.ow
    },
    // 重置画布大小
    resetSize() {
      this.canv.setZoom(this.zoom)
      this.canv.setWidth(this.cw)
      this.canv.setHeight(this.ch)
    },
    // 重置画布位置
    resetPos() {
      const tw = $('#canvWrap').width()
      const th = $('#canvWrap').height()
      this.$refs.canvMain.style.left = `${tw / 2 - this.cw / 2}px`
      this.$refs.canvMain.style.top = `${th / 2 - this.ch / 2}px`
    },
    // 添加本地图片
    addImg() {
      $('#imgInput').click()
    },
    // 更改画布背景图片
    changeCanvImg() {
      $('#canvImgInput').click()
    },
    // 从文件加载图片至canvas
    uploadImgChange() {
      // 获取文件
      const inputEle = document.getElementById('imgInput')
      this.imgFile = inputEle.files[0]
      // 从reader中获取选择文件的src
      if (/\.(jpe?g|png|gif|webp)$/i.test(this.imgFile.name)) {
        const formData = new FormData()
        formData.append('pptimg', this.imgFile)
        upload(formData)
          .then((res) => {
            const url = res.data.data.path
            fabric.Image.fromURL(
              url,
              (img) => {
                img.setSrc(url, () => {}, {
                  crossOrigin: 'anonymous'
                })
                this.canv.add(img)
                this.imgMenu = false
                this.canv.renderAll()
                this.recordOper()
              },
              {
                left: 0,
                top: 0,
                fill: '#00000000',
                stroke: '#000000',
                strokeWidth: 0,
                strokeUniform: true
              }
            )
          })
          .catch(() => {})
      }
    },
    uploadCanvImgChange() {
      // 获取文件
      const inputEle = document.getElementById('canvImgInput')
      this.imgFile = inputEle.files[0]
      // 从reader中获取选择文件的src
      if (/\.(jpe?g|png|gif|webp)$/i.test(this.imgFile.name)) {
        const formData = new FormData()
        formData.append('pptimg', this.imgFile)
        upload(formData)
          .then((res) => {
            const url = res.data.data.path
            fabric.Image.fromURL(url, (img) => {
              img.setSrc(url, () => {}, {
                crossOrigin: 'anonymous'
              })
              if (img.width / img.height >= this.ow / this.oh) {
                const scale = this.oh / img.height
                img.set({
                  scaleX: scale,
                  scaleY: scale
                })
                this.canv.setBackgroundImage(
                  img,
                  this.canv.renderAll.bind(this.canv),
                  {
                    left: (this.ow - img.width * scale) / 2
                  }
                )
              } else {
                const scale = this.ow / img.width
                img.set({
                  scaleX: scale,
                  scaleY: scale
                })
                this.canv.setBackgroundImage(
                  img,
                  this.canv.renderAll.bind(this.canv),
                  {
                    top: (this.oh - img.height * scale) / 2
                  }
                )
              }
              this.canv.renderAll()
              this.recordOper()
            })
          })
          .catch(() => {})
      }
    },
    // 设置网络图片为画布背景
    addCanvImg() {
      if (!this.$refs.canvImgUrlForm.validate()) {
        return
      }
      fabric.Image.fromURL(this.canvImgUrl, (img) => {
        img.setSrc(this.canvImgUrl, () => {}, {
          crossOrigin: 'anonymous'
        })
        if (img.width / img.height >= this.ow / this.oh) {
          const scale = this.oh / img.height
          img.set({
            scaleX: scale,
            scaleY: scale
          })
          this.canv.setBackgroundImage(
            img,
            this.canv.renderAll.bind(this.canv),
            {
              left: (this.ow - img.width * scale) / 2
            }
          )
        } else {
          const scale = this.ow / img.width
          img.set({
            scaleX: scale,
            scaleY: scale
          })
          this.canv.setBackgroundImage(
            img,
            this.canv.renderAll.bind(this.canv),
            {
              top: (this.oh - img.height * scale) / 2
            }
          )
        }
        this.canv.renderAll()
        this.recordOper()
      })
    },
    // 添加网络图片
    addImgUrl() {
      if (!this.$refs.imgUrlForm.validate()) {
        return
      }
      fabric.Image.fromURL(
        this.imgUrl,
        (img) => {
          img.setSrc(this.imgUrl, () => {}, {
            crossOrigin: 'anonymous'
          })
          this.canv.add(img)
          this.imgMenu = false
          this.imgUrl = ''
          this.canv.renderAll()
          this.recordOper()
        },
        {
          left: 0,
          top: 0,
          fill: '#00000000',
          stroke: '#000000',
          strokeWidth: 0,
          strokeUniform: true
        }
      )
    },
    // 添加链接
    addUrl() {
      if (!this.$refs.urlForm.validate()) {
        return
      }
      this.urlMenu = false
      this.prepareElement('url')
    },
    // 准备元素
    prepareElement(elType) {
      this.canv.isDrawingMode = false
      this.elType = elType
      if (this.elType === 'pen') {
        this.canv.isDrawingMode = true
        this.canv.freeDrawingBrush.color = '#000000'
        this.canv.freeDrawingBrush.width = 3
        this.tempObj = null
        this.tempLine = null
        return
      }
      if (this.elType === 'cursor') {
        this.noBanSel()
        this.tempObj = null
        this.tempLine = null
        return
      }
    },
    // 监听按钮改变
    operChange() {
      if (this.elOper === undefined || this.elOper === null) {
        this.elType = null
        this.elOper = null
      }
    },
    // 禁止对象选中
    banSel() {
      this.canv.selection = false
      this.canv.skipTargetFind = true
    },
    noBanSel() {
      this.canv.selection = true
      this.canv.skipTargetFind = false
    },
    // 当文本框文字数量为1时，设置整个文本框的样式与最后一个字相同
    setLastStyle(item) {
      if (item.text.length === 1) {
        const s = item.getSelectionStyles(0, 1, true)[0]
        item.fill = s.fill || '#000000FF'
        item.textBackgroundColor = s.textBackgroundColor || '#00000000'
        item.fontFamily = s.fontFamily || 'Times New Roman'
        item.fontSize = s.fontSize || 48
        item.stroke = s.stroke || '#000000'
        item.strokeWidth = s.strokeWidth || 0
      }
    },
    // 初始化画布
    initCanv() {
      this.canv = new fabric.Canvas('canvas', {
        backgroundColor: 'white',
        width: this.ow,
        height: this.oh
      })
      this.getSize()
      this.resetSize()
      this.resetPos()

      // 设置对象被选择时的默认框
      fabric.Object.prototype.set({
        padding: 0,
        borderColor: 'blue',
        cornerColor: 'white', //激活状态角落图标的填充颜色
        cornerStrokeColor: 'blue', //激活状态角落图标的边框颜色
        borderOpacityWhenMoving: 1,
        borderScaleFactor: 1,
        cornerSize: 8,
        cornerStyle: 'rect', //rect,circle
        centeredScaling: false, //放大缩小是否是以图形中心为放大原点
        centeredRotation: true, //旋转按钮旋转是否是中心为圆心旋转
        transparentCorners: false, //激活状态角落的图标是否透明
        rotatingPointOffset: 20, //旋转距旋转体的距离
        originX: 'left',
        originY: 'top',
        lockUniScaling: false, //只显示四角的操作
        hasRotatingPoint: true, //是否显示旋转按钮
        // borderDashArray: [3, 3], // 设置虚线间隔
        // minScaleLimit: 0.5, // 最小限制
        lockScalingFlip: true //控制缩放翻转
      })
      // fabric.Object.prototype.toObject = (function(toObject) {
      //   return function(){
      //     return fabric.util.object.extend(toObject.call(this), {
      //       a: 'ss'
      //     })
      //   }
      // })(fabric.Object.prototype.toObject)

      fabric.Textbox.prototype.set('editingBorderColor', 'blue')
      fabric.Textbox.prototype.set('padding', 20)
      fabric.Textbox.prototype.lockScalingY = true
      fabric.Textbox.prototype.setControlsVisibility({
        bl: false,
        br: false,
        tl: false,
        tr: false,
        mb: false,
        mt: false
      })
      this.canv.on('path:created', (e) => {
        // 需要添加缩放对象时保持线条宽度不变
        e.path.set('strokeUniform', true)
        this.recordOper()
      })
      this.canv.on('text:editing:entered', (e) => {
        // 这里是为了保证文本框中文字清空后，保证文本框的样式和删除前最后一个文字保持一致
        this.setLastStyle(e.target)
      })
      // 为文本框对象添加光标改变时的事件，将结果反馈给右边的设置栏
      this.canv.on('text:selection:changed', (e) => {
        this.setLastStyle(e.target)
        // 这里是为了减缓文字改变时卡顿的现象
        if (this.delayOper) {
          clearTimeout(this.delayOper)
        }
        this.delayOper = setTimeout(() => {
          const t = e.target
          const ed = t.selectionEnd || 1
          const s = t.getSelectionStyles(ed - 1, ed, true)[0]
          this.fillColor = s.fill || '#000000FF'
          this.bkColor = s.textBackgroundColor || '#00000000'
          this.fontFamily = s.fontFamily || 'Times New Roman'
          this.fontSize = s.fontSize || 48
          this.lineHeight = t.get('lineHeight') * 10 || 0
          this.charSpacing = t.get('charSpacing') / 100 || 0
          this.strokeColor = s.stroke || '#000000'
          this.strokeWidth = s.strokeWidth || 0
        }, 200)
      })

      // 选中元素是否置顶
      this.canv.preserveObjectStacking = true
      // 进入画布
      $('.upper-canvas').on('mouseenter', () => {
        this.isHoverCanv = true
        if (!this.elType || this.elType === 'pen' || this.elType === 'cursor') {
          return
        }
        this.banSel()
        if (this.elType === 'text') {
          this.tempObj = new fabric.Textbox('文本框', {
            fill: '#000000FF',
            stroke: '#BBBBBB',
            strokeWidth: 0,
            strokeUniform: true,
            selectable: false,
            hoverCursor: 'default',
            shadow: new fabric.Shadow({
              color: '#000000FF',
              blur: 0,
              offsetX: 0,
              offsetY: 0
            }),
            lineHeight: 1.1,
            fontSize: 48,
            opacity: 1
          })
        } else if (this.elType === 'url') {
          this.tempObj = new fabric.Textbox(this.webName, {
            underline: true,
            fill: '#0000FFFF',
            stroke: '#BBBBBB',
            strokeWidth: 0,
            strokeUniform: true,
            webUrl: this.webUrl,
            selectable: false,
            hoverCursor: 'default',
            shadow: new fabric.Shadow({
              color: '#000000FF',
              blur: 0,
              offsetX: 0,
              offsetY: 0
            }),
            lineHeight: 1.1,
            opacity: 1,
            fontSize: 48
          })
        } else if (this.elType === 'rect') {
          this.tempObj = new fabric.Rect({
            fill: '#BBBBBBFF',
            stroke: '#000000',
            strokeWidth: 0,
            strokeUniform: true,
            width: 200,
            height: 200,
            selectable: false,
            hoverCursor: 'default',
            rx: 0,
            ry: 0,
            shadow: new fabric.Shadow({
              color: '#000000FF',
              blur: 0,
              offsetX: 0,
              offsetY: 0
            }),
            opacity: 1
          })
        } else if (this.elType === 'circle') {
          this.tempObj = new fabric.Circle({
            fill: '#BBBBBBFF',
            stroke: '#000000',
            strokeWidth: 0,
            strokeUniform: true,
            radius: 100,
            selectable: false,
            hoverCursor: 'default',
            shadow: new fabric.Shadow({
              color: '#000000FF',
              blur: 0,
              offsetX: 0,
              offsetY: 0
            }),
            opacity: 1
          })
        } else if (this.elType === 'triangle') {
          this.tempObj = new fabric.Triangle({
            fill: '#BBBBBBFF',
            stroke: '#000000',
            strokeWidth: 0,
            strokeUniform: true,
            width: 200,
            height: 200,
            selectable: false,
            hoverCursor: 'default',
            shadow: new fabric.Shadow({
              color: '#000000FF',
              blur: 0,
              offsetX: 0,
              offsetY: 0
            }),
            opacity: 1
          })
        } else if (
          this.elType === 'line' ||
          this.elType === 'arrow' ||
          this.elType === 'arrowTop' ||
          this.elType === 'arrowBottom'
        ) {
          this.tempObj = null
        }
        if (this.tempObj) {
          this.canv.add(this.tempObj)
        }
      })
      // 离开画布
      $('.upper-canvas').on('mouseleave', () => {
        this.isHoverCanv = false
        this.noBanSel()
        if (this.tempObj) {
          this.canv.remove(this.tempObj)
        }
      })
      // 对象变化
      this.canv.on('object:modified', (e) => {
        this.recordOper()
        // 对象变化时，右侧编辑区实时响应数据变化
        if (e.target.get('type') === 'activeSelection') {
          return
        }
        const t = e.target
        const sy = Math.min(
          t.aCoords.bl.y,
          t.aCoords.br.y,
          t.aCoords.tl.y,
          t.aCoords.tr.y
        )
        const sx = Math.min(
          t.aCoords.bl.x,
          t.aCoords.br.x,
          t.aCoords.tl.x,
          t.aCoords.tr.x
        )
        this.objX = Math.round(t.left - (t.left - sx))
        this.objY = Math.round(t.top - (t.top - sy))
        this.objAngle = Math.round(t.angle || 0)
        // 因为边框占了一部分宽度，所以这里要计算实际宽高
        if (!['line', 'path', 'textbox'].includes(this.nowType)) {
          this.objWidth = Math.round(
            e.target.width * e.target.scaleX - e.target.strokeWidth || 200
          )
          this.objHeight = Math.round(
            e.target.height * e.target.scaleY - e.target.strokeWidth || 200
          )
        }
        // 因为渲染出来的图形的圆角会随scale有所变化，这里是为了保持渲染之后仍然保持
        if (this.nowType === 'rect') {
          e.target.set('rx', parseInt(this.radiusX) / t.get('scaleX') || 0)
          e.target.set('ry', parseInt(this.radiusY) / t.get('scaleY') || 0)
        }
      })
      this.canv.on('selection:cleared', () => {
        this.selectedCanvObjs = []
      })
      // url对象选中
      this.canv.on('selection:created', () => {
        // 为每个对象添加id
        this.canv.getObjects().forEach((item) => {
          if (!item.id) {
            this.makeObjId(item)
          }
        })
        // 更新对象列表参数
        this.getActiveIndex()
        // 更新编辑面板参数
        this.getActiveData()
        // 将右边的面板重置
        this.resetTab()
      })
      // url对象切换
      this.canv.on('selection:updated', () => {
        // 更新对象列表
        this.getActiveIndex()
        // 更新编辑区参数
        this.getActiveData()
        // 将右边的面板重置
        this.resetTab()
      })
      // url对象清除
      this.canv.on('selection:cleared', () => {
        this.nowType = 'canvas'
        // 恢复默认编辑面板参数
        this.fillColor = '#FF0000FF'
        this.strokeColor = '#FF0000'
        this.strokeWidth = 0
      })
      // 安放元素前在画布上的移动
      this.canv.on('mouse:move', (e) => {
        if (!this.elType) {
          return
        }
        // 如果为画直线
        if (this.elType === 'line') {
          if (!this.isDown) {
            return
          }
          if (this.tempLine) {
            this.canv.remove(this.tempLine)
          }
          this.tempLine = new fabric.Line(
            [
              this.beginPoint.x,
              this.beginPoint.y,
              e.absolutePointer.x,
              e.absolutePointer.y
            ],
            {
              fill: '#FFFFFF00',
              stroke: '#000000',
              strokeWidth: 3,
              strokeUniform: true,
              selectable: false,
              hoverCursor: 'default',
              opacity: 1
            }
          )
          this.canv.add(this.tempLine)
          return
        }
        if (this.elType === 'arrow') {
          if (!this.isDown) {
            return
          }
          if (this.tempLine) {
            this.canv.remove(this.tempLine)
          }
          const ex = e.absolutePointer.x
          const ey = e.absolutePointer.y
          const bx = this.beginPoint.x
          const by = this.beginPoint.y
          const theta = Math.atan2(ey - by, ex - bx)
          const alpha = Math.PI / 6
          const h = 20
          const d = 10
          const tx = ex - h * Math.cos(theta - alpha)
          const ty = ey - h * Math.sin(theta - alpha)
          const tx1 = ex - h * Math.cos(theta + alpha)
          const ty1 = ey - h * Math.sin(theta + alpha)
          const topx = ex + d * Math.cos(theta)
          const topy = ey + d * Math.sin(theta)
          this.tempLine = new fabric.Path(
            `M ${this.beginPoint.x} ${this.beginPoint.y}
            L ${e.absolutePointer.x} ${e.absolutePointer.y}
            L ${tx} ${ty}
            L ${topx} ${topy}
            L ${tx1} ${ty1}
            L ${e.absolutePointer.x} ${e.absolutePointer.y}`,
            {
              fill: '#000000FF',
              stroke: '#000000',
              strokeWidth: 3,
              strokeUniform: true,
              selectable: false,
              hoverCursor: 'default',
              opacity: 1
            }
          )
          this.canv.add(this.tempLine)
          return
        }
        if (this.elType === 'arrowTop') {
          if (!this.isDown) {
            return
          }
          if (this.tempLine) {
            this.canv.remove(this.tempLine)
          }
          const ex = e.absolutePointer.x
          const ey = e.absolutePointer.y
          const bx = this.beginPoint.x
          const by = this.beginPoint.y
          const theta = Math.atan2(ey - by, ex - bx)
          const alpha = Math.PI / 6
          const h = 20
          const d = 10
          const tx = ex - h * Math.cos(theta + alpha)
          const ty = ey - h * Math.sin(theta + alpha)
          const topx = ex + d * Math.cos(theta)
          const topy = ey + d * Math.sin(theta)
          this.tempLine = new fabric.Path(
            `M ${this.beginPoint.x} ${this.beginPoint.y}
            L ${e.absolutePointer.x} ${e.absolutePointer.y}
            L ${tx} ${ty}
            L ${topx} ${topy}
            L ${e.absolutePointer.x} ${e.absolutePointer.y}`,
            {
              fill: '#000000FF',
              stroke: '#000000',
              strokeWidth: 3,
              strokeUniform: true,
              selectable: false,
              hoverCursor: 'default',
              opacity: 1
            }
          )
          this.canv.add(this.tempLine)
          return
        }
        if (this.elType === 'arrowBottom') {
          if (!this.isDown) {
            return
          }
          if (this.tempLine) {
            this.canv.remove(this.tempLine)
          }
          const ex = e.absolutePointer.x
          const ey = e.absolutePointer.y
          const bx = this.beginPoint.x
          const by = this.beginPoint.y
          const theta = Math.atan2(ey - by, ex - bx)
          const alpha = Math.PI / 4
          const h = 20
          const d = 5
          const tx = ex - h * Math.cos(theta - alpha)
          const ty = ey - h * Math.sin(theta - alpha)
          const topx = ex + d * Math.cos(theta)
          const topy = ey + d * Math.sin(theta)
          this.tempLine = new fabric.Path(
            `M ${this.beginPoint.x} ${this.beginPoint.y}
            L ${e.absolutePointer.x} ${e.absolutePointer.y}
            L ${tx} ${ty}
            L ${topx} ${topy}
            L ${e.absolutePointer.x} ${e.absolutePointer.y}`,
            {
              fill: '#000000FF',
              stroke: '#000000',
              strokeWidth: 3,
              strokeUniform: true,
              selectable: false,
              hoverCursor: 'default',
              opacity: 1
            }
          )
          this.canv.add(this.tempLine)
          return
        }
        // 还未添加对象时移动
        if (!this.tempObj) {
          return
        }
        this.tempObj.set('left', e.absolutePointer.x)
        this.tempObj.set('top', e.absolutePointer.y)
        this.tempObj.setCoords()
        this.canv.renderAll()
      })
      // 安放元素
      this.canv.on('mouse:down', (e) => {
        // 添加线条
        if (
          this.elType === 'line' ||
          this.elType === 'arrow' ||
          this.elType === 'arrowTop' ||
          this.elType === 'arrowBottom'
        ) {
          this.isDown = true
          this.beginPoint.x = e.absolutePointer.x
          this.beginPoint.y = e.absolutePointer.y
          // 将右边的面板重置
          this.resetTab()
          return
        }
        // 添加形状
        if (!this.tempObj) {
          return
        }
        this.noBanSel()
        this.tempObj.selectable = true
        this.tempObj.hoverCursor = 'move'
        this.canv.setActiveObject(this.tempObj)
        this.tempObj = null
        this.elType = null
        this.elOper = null
        // 将右边的面板重置
        this.resetTab()
        // 记录操作
        this.recordOper()
      })

      // 暂时不用，使用点击按钮将元素移动到画布中央
      this.canv.on('mouse:up', (e) => {
        // 安放线条
        this.noBanSel()
        if (
          this.elType === 'line' ||
          this.elType === 'arrow' ||
          this.elType === 'arrowTop' ||
          this.elType === 'arrowBottom'
        ) {
          if (!this.tempLine) {
            this.elType = null
            this.elOper = null
            return
          }
          this.isDown = false
          this.tempLine.selectable = true
          this.tempLine.hoverCursor = 'move'
          this.canv.setActiveObject(this.tempLine)
          this.tempLine = null
          this.elType = null
          this.elOper = null
          // 记录操作
          this.recordOper()
        }
        // 阻止对象移动到画布外面
        if (!e.target) {
          return
        }
        const obj = e.target
        const left = obj.left
        const top = obj.top
        const my = Math.max(
          obj.aCoords.bl.y,
          obj.aCoords.br.y,
          obj.aCoords.tl.y,
          obj.aCoords.tr.y
        )
        const sy = Math.min(
          obj.aCoords.bl.y,
          obj.aCoords.br.y,
          obj.aCoords.tl.y,
          obj.aCoords.tr.y
        )
        const mx = Math.max(
          obj.aCoords.bl.x,
          obj.aCoords.br.x,
          obj.aCoords.tl.x,
          obj.aCoords.tr.x
        )
        const sx = Math.min(
          obj.aCoords.bl.x,
          obj.aCoords.br.x,
          obj.aCoords.tl.x,
          obj.aCoords.tr.x
        )
        if (my < this.padding) {
          obj.set('top', this.padding - (my - top))
        }
        if (sy > this.oh - this.padding) {
          obj.set('top', this.oh - this.padding + (top - sy))
        }
        if (mx < this.padding) {
          obj.set('left', this.padding - (mx - left))
        }
        if (sx > this.ow - this.padding) {
          obj.set('left', this.ow - this.padding + (left - sx))
        }
        obj.setCoords()
        this.canv.renderAll()
      })

      this.resetPos()
    },
    // 删除对象
    deleteObj() {
      // 阻止在更改对象属性时删除对象
      if ($(document.activeElement).parents().hasClass('right-drawer')) {
        return
      }
      // 这里是为了保证文本框在编辑时不被删除
      this.canv.getActiveObjects().map((item) => {
        if (item.type === 'textbox' && item.isEditing) {
          // 这里是为了保证文字框删除时实时更新右边的属性栏
          // 删除的时候光标改变事件并不会触发，所以要单独监听
          setTimeout(() => {
            this.getActiveData({
              selected: [item]
            })
            // 这里是为了保证文本框中文字清空后，保证文本框的样式和删除前最后一个文字保持一致
            this.setLastStyle(item)
          })
          return
        }
        this.canv.remove(item)
        this.canv.discardActiveObject().renderAll()
        this.recordOper()
      })
    },
    // 切换全屏非全屏
    changeScreen() {
      if (this.expendIcon === 'zoom_out_map') {
        this.fullScreen()
        this.expendIcon = 'zoom_in_map'
      } else {
        this.exitScreen()
        this.expendIcon = 'zoom_out_map'
      }
    },
    //全屏
    fullScreen() {
      const el = document.documentElement
      const rfs =
        el.requestFullScreen ||
        el.webkitRequestFullScreen ||
        el.mozRequestFullScreen ||
        el.msRequestFullScreen

      if (rfs) {
        rfs.call(el)
      }
    },
    //退出全屏
    exitScreen() {
      const el = document
      const cfs =
        el.cancelFullScreen ||
        el.webkitCancelFullScreen ||
        el.mozCancelFullScreen ||
        el.exitFullScreen

      if (cfs) {
        cfs.call(el)
      }
    },
    mountEvent() {
      $(window).on('beforeunload', () => {
        if (!/.*\/ppt\/{0,1}.*/.test(window.location.href)) {
          return
        }
        if (this.webFrame) {
          this.webFrame.close()
        }
        return '请确保已经保存数据'
      })
      $(document).on('keydown', (e) => {
        if (!/.*\/ppt\/{0,1}.*/.test(window.location.href)) {
          return
        }
        // // 禁用f11全屏
        // if (e.keyCode === 122) {
        //   e.preventDefault()
        // }
        this.ctrlKey = navigator.platform.match('Mac') ? e.metaKey : e.ctrlKey
        if (e.keyCode === 46 || e.keyCode === 8) {
          // e.preventDefault()
          this.deleteObj()
        }
        if (this.ctrlKey && e.keyCode === 83) {
          e.preventDefault()
        }
        if (this.ctrlKey && e.keyCode === 90) {
          this.undoOper()
        }
        if (this.ctrlKey && e.keyCode === 89) {
          this.restoreOper()
        }
      })
      $(document).on('keyup', (e) => {
        if (!/.*\/ppt\/{0,1}.*/.test(window.location.href)) {
          return
        }
        this.ctrlKey = navigator.platform.match('Mac') ? e.metaKey : e.ctrlKey
        if (this.ctrlKey && e.keyCode === 83) {
          this.storePPT()
        }
        // 剪切
        if (this.ctrlKey && e.keyCode === 88) {
          if (!this.canv.getActiveObject()) {
            return
          }
          const t = this.canv.getActiveObject()
          if (t.type === 'textbox' && t.isEditing) {
            return
          }
          t.clone((cloned) => {
            if (t.webUrl) {
              cloned.webUrl = t.webUrl
            }
            this.clipboard = cloned
          })
          this.canv.remove(t)
          this.recordOper()
        }
        // 复制
        if (this.ctrlKey && e.keyCode === 67) {
          if (!this.canv.getActiveObject()) {
            return
          }
          const t = this.canv.getActiveObject()
          if (t.type === 'textbox' && t.isEditing) {
            return
          }
          t.clone((cloned) => {
            if (t.webUrl) {
              cloned.webUrl = t.webUrl
            }
            this.clipboard = cloned
          })
        }
        // 粘贴
        if (this.ctrlKey && e.keyCode === 86) {
          if (!this.clipboard) {
            return
          }
          const t = this.canv.getActiveObject()
          if (t && t.type === 'textbox' && t.isEditing) {
            this.clipboard = null
            return
          }
          this.clipboard.clone((clonedObj) => {
            this.canv.discardActiveObject()
            clonedObj.set({
              left: clonedObj.left + 10,
              top: clonedObj.top + 10,
              evented: true
            })
            if (clonedObj.type === 'activeSelection') {
              clonedObj.canvas = this.canv
              clonedObj.forEachObject((obj) => {
                this.canv.add(obj)
              })
              clonedObj.setCoords()
            } else {
              if (this.clipboard.webUrl) {
                clonedObj.webUrl = this.clipboard.webUrl
              }
              this.canv.add(clonedObj)
            }
            this.clipboard.top += 10
            this.clipboard.left += 10
            this.canv.setActiveObject(clonedObj)
            this.canv.requestRenderAll()
            this.clipboard = null
            this.recordOper()
          })
        }
      })
      $(document).on('contextmenu', (e) => {
        if (!/.*\/ppt\/{0,1}.*/.test(window.location.href)) {
          return
        }
        e.preventDefault()
      })
      $(document).on('mousedown', (e) => {
        if (!/.*\/ppt\/{0,1}.*/.test(window.location.href)) {
          return
        }
        this.isPageActive = true
        // 当点击对象不是指定对象时，取消当前的对象选择
        if (
          !$(e.target).hasClass('canv-main') &&
          !$(e.target).parents().hasClass('canv-main') &&
          !$(e.target).hasClass('right-drawer') &&
          !$(e.target).parents().hasClass('right-drawer') &&
          !$(e.target).hasClass('right-drawer-out') &&
          !$(e.target).parents().hasClass('right-drawer-out') &&
          !$(e.target).parents().hasClass('out-menu-1') &&
          !$(e.target).parents().hasClass('out-menu-2') &&
          !$(e.target).parents().hasClass('out-menu-3') &&
          !$(e.target).hasClass('left-drawer') &&
          !$(e.target).parents().hasClass('left-drawer') &&
          !$(e.target).hasClass('canv-menu') &&
          !$(e.target).parents().hasClass('canv-menu') &&
          !$(e.target).hasClass('obj-menu') &&
          !$(e.target).parents().hasClass('obj-menu')
        ) {
          this.canv.discardActiveObject().renderAll()
        }
        // 当点击对象不是指定对象时，已选择的对象清空
        if (
          $(e.target).hasClass('upper-canvas') ||
          $(e.target).hasClass('oper-btn') ||
          $(e.target).parents().hasClass('oper-btn')
        ) {
          return
        }
        this.noBanSel()
        this.elType = null
        this.elOper = null
      })

      window.onresize = () => {
        if (!/.*\/ppt\/{0,1}.*/.test(window.location.href)) {
          return
        }
        // 获取全屏状态
        if ($(window).height() === window.screen.height) {
          this.expendIcon = 'zoom_in_map'
        } else {
          this.expendIcon = 'zoom_out_map'
        }
        setTimeout(() => {
          this.getSize()
          this.resetSize()
          this.resetPos()
        }, 300)
      }

      window.addEventListener('paste', (e) => {
        if (!/.*\/ppt\/{0,1}.*/.test(window.location.href)) {
          return
        }
        if (this.canv.getActiveObjects().length !== 0) {
          return
        }
        if (this.clipboard) {
          return
        }
        if (!e.clipboardData) {
          return
        }
        const imgs = e.clipboardData.items
        if (!imgs || imgs.length === 0) {
          return
        }
        const img = imgs[imgs.length - 1]
        if (img.type.indexOf('image') !== -1) {
          this.$layer.show()
          const formData = new FormData()
          formData.append('pptimg', img.getAsFile())
          upload(formData)
            .then((res) => {
              const url = res.data.data.path
              fabric.Image.fromURL(
                url,
                (img) => {
                  img.setSrc(url, () => {}, {
                    crossOrigin: 'anonymous'
                  })
                  this.canv.add(img)
                  this.$layer.hide()
                },
                {
                  left: 0,
                  top: 0,
                  fill: '#00000000',
                  stroke: '#000000',
                  strokeWidth: 0,
                  strokeUniform: true
                }
              )
            })
            .catch(() => {
              this.$layer.hide()
            })
        }
        // 这里是转化为base64，最初用的这种方法
        // for (let i = 0; i < imgs.length; i++) {
        //   if (imgs[i].type.indexOf('image') === -1) {
        //     continue
        //   }
        //   const imgObj = imgs[i].getAsFile()
        //   const reader = new FileReader()
        //   const that = this
        //   reader.addEventListener(
        //     'load',
        //     function () {
        //       fabric.Image.fromURL(
        //         this.result,
        //         (img) => {
        //           that.canv.add(img)
        //         },
        //         {
        //           left: 0,
        //           top: 0,
        //           fill: '#00000000',
        //           stroke: '#000000',
        //           strokeWidth: 0,
        //           strokeUniform: true
        //         }
        //       )
        //     },
        //     false
        //   )
        //   reader.readAsDataURL(imgObj)
        //   break
        // }
      })
    }
  }
}
</script>

<style scoped>
.format-control {
  display: flex;
  justify-content: space-between;
}
.format-control button {
  flex-shrink: 1;
  width: 100%;
}
::v-deep .right-drawer .v-expansion-panels {
  z-index: unset;
}
::v-deep .right-drawer .v-expansion-panel-header {
  padding: 0 14px;
}
::v-deep .right-drawer .v-expansion-panel--active > .v-expansion-panel-header {
  min-height: 48px;
}
::v-deep .right-drawer .v-expansion-panel-content__wrap {
  padding: 0 12px 14px;
}
::v-deep .right-tab .v-btn-toggle {
  overflow: hidden;
  display: flex;
  width: 100%;
}
::v-deep .right-tab .v-btn-toggle > .v-btn {
  flex: auto;
  width: 100%;
}
::v-deep .right-tab .v-btn:not(.v-btn--round).v-size--default {
  min-width: 0 !important;
}
::v-deep .fill-color-btn.v-btn:focus::before {
  opacity: 0 !important;
}
.canv-wrap {
  position: relative;
  overflow: auto;
  width: 100%;
  height: 100%;
}
.has-border {
  border: 2px solid blue;
}
iframe {
  display: block;
  margin: 0;
  padding: 0;
  border: none;
  outline: none;
}
.ppt-img {
  width: 160px;
  height: 90px;
}
</style>
